O blog da AWS

Como criar uma autorização refinada usando o Amazon Cognito, o API Gateway e o IAM

Por Artem Lovan, arquiteto sênior de soluções em Nova York na AWS.

 

Autorizar a funcionalidade de uma aplicação com base na associação a um grupo é uma melhor prática. Se estiver criando APIs com o  Amazon API Gateway e precisar de um controle de acesso refinado para os usuários, você pode usar o  Amazon Cognito. O Amazon Cognito permite que você use grupos para criar um conjunto de usuários, o que geralmente é feito para definir as permissões para esses usuários. Nesta publicação mostraremos como criar uma autorização refinada para proteger suas APIs usando o Amazon Cognito, o API Gateway e o  AWS Identity and Access Management (IAM).

Como desenvolvedor, você está criando uma aplicação voltada para o cliente, na qual os usuários farão login pela web ou dispositivo móvel e, por isso, suas APIs serão expostas por meio do API Gateway com serviços upstream. As APIs podem ser implantadas no Amazon Elastic Container Service (Amazon ECS)Amazon Elastic Kubernetes Service (Amazon EKS)AWS Lambda ou Elastic Load Balancing, e cada uma dessas opções encaminhará a solicitação às suas instâncias do Amazon Elastic Compute Cloud (Amazon EC2). Além disso, você pode usar serviços on-premises conectados ao seu ambiente da Amazon Web Services (AWS) por meio de uma AWS VPN ou do AWS Direct Connect. É importante ter controles refinados para cada endpoint de API e utilizar HTTP. Por exemplo, o usuário deve ter permissão para fazer uma solicitação GET a um endpoint, mas não deve ter permissão para fazer uma solicitação POST ao mesmo endpoint. Como prática recomendada, você deve atribuir usuários a grupos e usar a associação a grupos para permitir ou negar acesso aos seus serviços de API.

Visão geral da solução

Neste blog você aprenderá a usar um grupo de usuários do Amazon Cognito como diretório de usuários e permitir que os usuários autentiquem e adquiram o JSON Web Token (JWT) para passar para o API Gateway. O JWT é usado para identificar a qual grupo o usuário pertence, pois o mapeamento de um grupo para uma política do IAM exibirá os direitos de acesso concedidos ao grupo.

Nota: a solução funciona de forma semelhante se o Amazon Cognito federar usuários com um provedor de identidades (IdP) externo, como Ping, Active Directory ou Okta, em vez de ser ele mesmo um IdP. Para saber mais, consulte Adding User Pool Sign-in Through a Third Party (Adicionar login do grupo de usuários por meio de terceiros). Além disso, se quiser usar grupos de um IdP externo para conceder acesso, você pode encontrar a descrição de como fazer isso em Role-based access control using Amazon Cognito and an external identity provider (Controle de acesso baseado em função usando o Amazon Cognito e um provedor de identidades externo).

A figura a seguir mostra a arquitetura básica e o fluxo de informações para solicitações do usuário.

Figure 1: User request flowFigura 1: Fluxo de solicitação do usuário

Vamos analisar o fluxo de solicitações para entender o que ocorre em cada etapa, conforme mostrado na figura 1:

  1. Um usuário faz login e adquire um token JWT do Amazon Cognito, um token de acesso e um token de atualização. Para saber mais sobre cada token, consulte Using tokens with user pools (Como usar tokens com grupos de usuários).
  2. Uma solicitação RestAPI é feita e um Bearer Token, o qual nessa solução é um token de acesso, é passado nos cabeçalhos.
  3. O API Gateway encaminha a solicitação para um autorizador do Lambda (Lambda authorizer), também conhecido como autorizador customizado (custom authorizer).
  4. O autorizador do Lambda verifica o Amazon Cognito JWT usando a chave pública do Amazon Cognito. Na invocação inicial do Lambda, a chave pública é baixada do Amazon Cognito e armazenada em cache. As chamadas subsequentes usarão a chave pública do cache.
  5. O autorizador do Lambda pesquisa o grupo do Amazon Cognito ao qual o usuário pertence no JWT e faz uma pesquisa no Amazon DynamoDB para obter a política mapeada para o grupo.
  6. O Lambda retorna a política e, opcionalmente, o contexto para o API Gateway. O contexto é um mapa contendo pares de chave-valor que você pode passar para o serviço upstream. Podem ser informações adicionais sobre o usuário, o serviço ou qualquer dado que forneça informações adicionais ao serviço upstream.
  7. O mecanismo de avaliação política do API Gateway avalia a política.
    Nota: o Lambda não é responsável por entender e avaliar a política. Essa responsabilidade recai sobre os recursos nativos do API Gateway.
  8. A solicitação é encaminhada para o serviço.
    Nota: para otimizar ainda mais o autorizador do Lambda, a política de autorização pode ser armazenada em cache ou desabilitada, dependendo de suas necessidades. Ao habilitar o cache, você pode melhorar a performance, pois a política de autorização será retornada do cache sempre que houver uma correspondência de chave de cache. Para saber mais, consulte Configure a Lambda authorizer using the API Gateway console (Configure um autorizador do Lambda usando o console do API Gateway).

Vamos ver mais de perto o exemplo de política a seguir, que é armazenado como parte de um item no DynamoDB.

{
   "Version":"2012-10-17",
   "Statement":[
      {
         "Sid":"PetStore-API",
         "Effect":"Allow",
         "Action":"execute-api:Invoke",
         "Resource":[
            "arn:aws:execute-api:*:*:*/*/*/petstore/v1/*",
            "arn:aws:execute-api:*:*:*/*/GET/petstore/v2/status"
         ],
         "Condition":{
            "IpAddress":{
               "aws:SourceIp":[
                  "192.0.2.0/24",
                  "198.51.100.0/24"
               ]
            }
         }
      }
   ]
}

Com base nessa política de exemplo, o usuário tem permissão para fazer chamadas para a API petstore. Para a versão v1, o usuário pode fazer solicitações a qualquer verbo e qualquer caminho, qual é expresso por um asterisco (*). Para a v2, o usuário só tem permissão para fazer uma solicitação GET para o caminhostatus. Para saber mais sobre como as políticas funcionam, consulte Output from an Amazon API Gateway Lambda authorizer (Output de um autorizador Lambda do Amazon API Gateway).

Conceitos básicos

Para essa solução, você precisa dos seguintes pré-requisitos:

Nota: recomendamos o uso de um ambiente virtual ou virtualenvwrapper para isolar a solução do restante de seu ambiente Python.

  • Um perfil ou usuário do IAM com permissões suficientes para criar um grupo de usuários do Amazon Cognito, perfil do IAM, Lambda, política do IAM, API Gateway e tabela do DynamoDB.
  • O repositório do GitHub para a solução. Você pode baixá-lo ou usar o seguinte comando do Git para baixá-lo no seu terminal.

Nota: esse código de exemplo deve ser usado para testar a solução e não deve ser usado na conta de produção.

 $ git clone https://github.com/aws-samples/amazon-cognito-api-gateway.git
 $ cd amazon-cognito-api-gateway

Use o comando a seguir para fazer o packaging do código Python para implantação no Lambda.

 $ bash ./helper.sh package-lambda-functions
 …
 Successfully completed packaging files.

Para implementar essa arquitetura de referência, você utilizará os seguintes serviços:

  • Amazon Cognito para oferecer suporte a um grupo de usuários para a base de usuários.
  • API Gateway para proteger e publicar as APIs.
  • Lambda para servir as APIs.

Nota: essa solução foi testada nas regiões us-east-1, us-east-2, us-west-2, ap-southeast-1 e ap-southeast-2. Antes de selecionar uma região, verifique quais serviços necessários (Amazon Cognito, API Gateway e Lambda) estão disponíveis nessas regiões.

Vamos analisar cada serviço e como eles serão usados antes de criar os recursos para essa solução.

Grupo de usuários do Amazon Cognito

Um grupo de usuários (User Pool) é um diretório de usuários no Amazon Cognito. Com um grupo de usuários, os usuários podem fazer login em sua aplicação da web ou dispositivo móvel por meio do Amazon Cognito. Você usa o diretório de usuários do Amazon Cognito diretamente, pois essa solução de exemplo cria um usuário do Amazon Cognito. No entanto, seus usuários também podem fazer login por meio de IdPs sociais, OpenID Connect (OIDC) e SAML.

Lambda como serviço de apoio de API

Inicialmente, você cria uma função Lambda que atende às suas APIs. O API Gateway encaminha todas as solicitações para a função Lambda para atender às solicitações.

Uma instância do API Gateway e integração com o Lambda

Depois, você cria uma instância do API Gateway e a integra à função Lambda que você criou. Essa instância do API Gateway serve como ponto de entrada para o serviço upstream. O comando bash a seguir cria um grupo de usuários do Amazon Cognito, uma função Lambda e uma instância do API Gateway. Em seguida, o comando configura a integração de proxy com o Lambda e implanta um estágio do API Gateway.

Implantar a solução exemplo

No diretório onde você baixou o código de exemplo do GitHub, execute o comando a seguir para gerar uma senha de usuário aleatória do Amazon Cognito e criar os recursos descritos na seção anterior.

 $ bash ./helper.sh cf-create-stack-gen-password
 ...
 Successfully created CloudFormation stack.

Quando o comando é concluído, ele retorna uma mensagem confirmando a criação da pilha(stack).

Validar a criação de usuários do Amazon Cognito

Para validar que um usuário do Amazon Cognito foi criado, execute o seguinte comando para abrir a interface do usuário do Amazon Cognito em seu navegador. Depois, faça login com suas credenciais.

Nota: quando você executa esse comando, ele retorna o nome de usuário e a senha que você deve usar para fazer login.

 $ bash ./helper.sh open-cognito-ui
  Opening Cognito UI. Please use following credentials to login:
  Username: cognitouser
  Password: xxxxxxxx

Como alternativa, você pode abrir a pilha do CloudFormation e obter a URL da interface do usuário hospedada do Amazon Cognito das saídas da pilha (Outputs). A URL é o valor atribuído à variável CognitoHostedUiUrl.

Figure 2: CloudFormation Outputs - CognitoHostedUiUrlFigura 2: Saídas do CloudFormation: CognitoHostedUiUrl

Validar o Amazon Cognito JWT ao fazer login

Como não instalamos uma aplicação da web que responderia à solicitação de redirecionamento, o Amazon Cognito redirecionará para o localhost, o que pode parecer um erro. Após um login bem sucedido, uma URL semelhante à seguinte aparecerá na barra de navegação do seu navegador:

http://localhost/#id_token=eyJraWQiOiJicVhMYWFlaTl4aUhzTnY3W...

Teste da configuração da API

Antes de proteger a API com o Amazon Cognito para que somente usuários autorizados possam acessá-la, vamos verificar se a configuração está correta e se a API é atendida pelo API Gateway. O comando a seguir faz uma solicitação curl ao API Gateway para recuperar dados do serviço da API.

 $ bash ./helper.sh curl-api
{"pets":[{"id":1,"name":"Birds"},{"id":2,"name":"Cats"},{"id":3,"name":"Dogs"},{"id":4,"name":"Fish"}]}

O resultado esperado é que a resposta seja uma lista de animais de estimação. Nesse caso, a configuração está correta: o API Gateway está servindo a API.

Proteger a API

Para proteger sua API, os itens a seguir são necessários:

  1. DynamoDB, para armazenar a política que será avaliada pelo API Gateway para tomar uma decisão de autorização.
  2. Uma função Lambda, para verificar o token de acesso do usuário e pesquisar a política no DynamoDB.

Vamos revisar todos os serviços antes de criar os recursos.

Autorizador do Lambda

Um autorizador do Lambda é um recurso do API Gateway que usa uma função Lambda para controlar o acesso a uma API. Um autorizador do Lambda é usado para implementar um esquema de autorização customizado que usa uma estratégia de autenticação de Bearer Token. Quando um cliente faz uma solicitação para uma das operações de API, o API Gateway chama o autorizador do Lambda. O autorizador do Lambda pega a identidade do chamador como entrada e retorna uma política do IAM como saída. A saída é a política retornada no DynamoDB e avaliada pelo API Gateway. Se não houver uma política mapeada para a identidade do chamador, o Lambda gerará uma política de rejeição e a solicitação será negada.

Tabela do DynamoDB

O DynamoDB é um banco de dados de documentos e chave-valor que oferece performance de um a nove milissegundos em qualquer escala. Isso é ideal para esse caso de uso de forma a garantir que o autorizador do Lambda possa processar rapidamente o Bearer Token, pesquisar a política e devolvê-la ao API Gateway. Para saber mais, consulte Control access for invoking an API (Controle o acesso para invocar uma API).

A etapa final é criar a tabela do DynamoDB para que o autorizador do Lambda procure a política, que é mapeada para um grupo do Amazon Cognito.

A figura 3 ilustra um item no DynamoDB. Os principais atributos são:

  • Grupo(group), que é usado para pesquisar a política.
  • Política(policy), que é retornada ao API Gateway para avaliar a política.

Figure 3: DynamoDB itemFigura 3: item do DynamoDB

Com base nessa política, o usuário que faz parte do grupo do Amazon Cognito pet-veterinarian tem permissão para fazer solicitações de API para os endpoints https://<domain>/<api-gateway-stage>/petstore/v1/* e https://<domain>/<api-gateway-stage>/petstore/v2/status apenas para solicitações GET.

Atualizar e criar recursos

Execute o comando a seguir para atualizar os recursos existentes e criar um autorizador do Lambda e uma tabela do DynamoDB.

 $ bash ./helper.sh cf-update-stack
Successfully updated CloudFormation stack.

Teste da configuração do autorizador customizado

Comece seu teste com a solicitação a seguir, que não inclui um token de acesso.

$ bash ./helper.sh curl-api
{"message":"Unauthorized"}

A solicitação é negada com a mensagem Não autorizado. Nesse ponto, o Amazon API Gateway espera um cabeçalho chamado Authorization (diferencia maiúsculas de minúsculas) na solicitação. Se não houver cabeçalho de autorização, a solicitação será negada antes de chegar ao autorizador do Lambda. Essa é uma forma de filtrar solicitações que não incluem as informações necessárias.

Use o comando a seguir para o próximo teste. Neste teste, você passa o cabeçalho necessário, mas o token é inválido porque não foi emitido pelo Amazon Cognito; é um token simples no formato JWT armazenado em ./helper.sh. Para saber mais sobre como decodificar e validar um JWT, consulte decode and verify an Amazon Cognito JSON token (decodifique e verifique um token JSON do Amazon Cognito).

$ bash ./helper.sh curl-api-invalid-token
{"Message":"User is not authorized to access this resource"}

Desta vez, a mensagem é diferente. O autorizador do Lambda recebeu a solicitação e identificou o token como inválido e respondeu com a mensagem O usuário não está autorizado a acessar este recurso.

Para ter sucesso em uma solicitação à API protegida, seu código precisará seguir as seguintes etapas:

  1. Usar um nome de usuário e senha para autenticar no grupo de usuários do Amazon Cognito.
  2. Adquirir os tokens (token de identidade, token de acesso e token de atualização).
  3. Fazer uma solicitação HTTPS (TLS) para o API Gateway e passar o token de acesso nos cabeçalhos.

Antes que a solicitação seja encaminhada para o serviço de API, o API Gateway recebe a solicitação e a passa para o autorizador do Lambda. O autorizador executa as seguintes etapas. Se alguma das etapas falhar, a solicitação será negada.

  1. Recupera as chaves públicas do Amazon Cognito.
  2. Armazena em cache as chaves públicas para que o autorizador do Lambda não precise fazer chamadas adicionais para o Amazon Cognito, desde que o ambiente de execução do Lambda não seja encerrado.
  3. Usa chaves públicas para verificar o token de acesso.
  4. Consulta a política no DynamoDB.
  5. Retorna a política ao API Gateway.

O token de acesso tem declarações como grupos atribuídos do Amazon Cognito, nome de usuário, uso de token e outros, conforme mostrado no exemplo a seguir (alguns campos foram removidos).

{
    "sub": "00000000-0000-0000-0000-0000000000000000",
    "cognito:groups": [
        "pet-veterinarian"
    ],
...
    "token_use": "access",
    "scope": "openid email",
    "username": "cognitouser"
}

Por fim, vamos fazer login programaticamente na interface do usuário do Amazon Cognito, adquirir um token de acesso válido e fazer uma solicitação ao API Gateway. Execute o comando a seguir para chamar a API protegida.

$ bash ./helper.sh curl-protected-api
{"pets":[{"id":1,"name":"Birds"},{"id":2,"name":"Cats"},{"id":3,"name":"Dogs"},{"id":4,"name":"Fish"}]}

Desta vez, você recebe uma resposta com dados do serviço da API. Vamos examinar as etapas que o código de exemplo executou:

  1. O autorizador do Lambda valida o token de acesso.
  2. O autorizador do Lambda procura a política no DynamoDB com base no nome do grupo que foi recuperado do token de acesso.
  3. O autorizador do Lambda passa a política do IAM de volta ao API Gateway.
  4. O API Gateway avalia a política do IAM e o efeito final é allow.
  5. O API Gateway encaminha a solicitação para o Lambda.
  6. O Lambda retorna a resposta.

Vamos continuar testando nossa política da figura 3. No documento de política, arn:aws:execute-api:*:*:*/*/GET/petstore/v2/status é o único endpoint da versão V2, o que significa que as solicitações para o endpoint /GET/petstore/v2/pets devem ser negadas. Execute o seguinte comando para testar.

 $ bash ./helper.sh curl-protected-api-not-allowed-endpoint
{"Message":"User is not authorized to access this resource"}

Nota: agora que você entendeu o controle de acesso refinado usando o grupo de usuários do Cognito, o API Gateway e a função Lambda e concluiu o teste, execute o seguinte comando para limpar todos os recursos associados a essa solução:

 $ bash ./helper.sh cf-delete-stack

Políticas avançadas do IAM para controlar ainda mais sua API

Com o IAM, você pode criar políticas avançadas para refinar ainda mais o acesso às suas APIs. Você pode saber mais sobre as chaves de condição que podem ser usadas no API Gateway, o uso delas em uma política do IAM com condições e como a lógica de avaliação de políticas determina se deve permitir ou negar uma solicitação.

Resumo

Nesta publicação, você aprendeu como o IAM e o Amazon Cognito podem ser usados para fornecer controle de acesso refinado para sua API por trás do API Gateway. Você pode usar essa abordagem para aplicar de forma transparente um controle refinado à sua API sem precisar modificar o código nela e para criar políticas avançadas usando as chaves de condição do IAM.

Se você tiver algum feedback sobre esta publicação, envie os comentários na seção Comentários abaixo. Se você tiver dúvidas sobre esta publicação, inicie um novo tópico no fórum do Amazon Cognito ou em Entre em contato com o AWS Support.

 

Este artigo foi traduzido do Blog da AWS em inglês.


Sobre o autor

Artem Lovan é arquiteto sênior de soluções em Nova York. Ele ajuda os clientes a arquitetar e otimizar aplicações na AWS. Seu envolvimento em TI inclui infraestrutura, rede, segurança, DevOps e desenvolvimento de software.

 

 

 

 

Revisores

Felipe Bortoletto é um arquiteto de soluções na AWS. Trabalha na Amazon desde 2017, em 2019 ele participou do programa Tech U que proveu o conhecimento e pratica necessário para ser promovido para arquiteto de soluções. Atualmente está trabalhando com o mercado financeiro e se especializando em segurança.

 

 

 

 

Murillo Tavares é Technical Account Manager na AWS Brasil. Trabalha na Amazon desde 2022 atendendo clientes que participam do Enterprise Support. Atualmente está trabalhando com o mercado financeiro e se especializando em bancos de dados.