serverless, aws, saas, IoT

Construindo um BBQ conectado serverless como SaaS - Parte 4 - AuthZ

2024-09-20
This post cover image
aws cloud serverless bbq iot saas

Este arquivo foi traduzido automaticamente por IA, erros podem ocorrer

Já abordamos a isolamento de dados e segurança em nível de linha em um ambiente SaaS multi-tenant. Agora, é hora de ver como lidar com autorização, o processo de controle de acesso a dados e funcionalidade com base em quem é o usuário e o que ele está autorizado a fazer.

Neste post, exploraremos a arquitetura de um Ponto de Decisão de Política (PDP) centralizado com Pontos de Aplicação de Política (PEPs) distribuídos para garantir uma autorização consistente e segura em toda a sua plataforma SaaS. Também explicaremos as diferenças chave entre Autenticação (AuthN) e Autorização (AuthZ).

Se você ainda não conferiu, aqui estão parte um, parte dois e parte três

Autenticação (AuthN) vs. Autorização (AuthZ)

Antes de entrarmos na construção da arquitetura, precisamos estabelecer um entendimento comum. É crucial distinguir entre Autenticação e Autorização, dois termos que muitas vezes são confundidos, e que eu tenho tido que explicar em muitas ocasiões, mas que servem propósitos muito diferentes.

Autenticação (AuthN)

Autenticação é sobre verificar a identidade. Ela responde à pergunta, Quem você é? Quando um usuário entra em nosso aplicativo SaaS, a autenticação garante que ele é quem diz ser. Isso pode envolver algo tão simples quanto um nome de usuário e senha ou uma autenticação multifator (MFA) mais complexa.

Nesta série, estamos usando Amazon Cognito User Pools para nossa autenticação.

Autorização (AuthZ)

Após a identidade do usuário ser autenticada, a autorização entra em ação. Este processo responde à pergunta, O que você pode fazer? A autorização determina quais recursos, dados e ações um usuário está autorizado a acessar com base em seus papéis e permissões.

Em um ambiente multi-tenant, fazer a autorização corretamente é importante. Precisamos garantir que os usuários só acessem os dados e funcionalidades relevantes para seu tenant e papel. É aí que entra um sistema de autorização robusto e escalável.

PDP Centralizado e PEPs Distribuídos: A Espinha Dorsal da Autorização

Agora que esclarecemos a diferença entre AuthN e AuthZ, vamos focar na construção de um sistema de autorização usando um PDP centralizado e PEPs distribuídos.

PDP Centralizado

Um Ponto de Decisão de Política (PDP) é onde todas as suas decisões de autorização são tomadas. É o cérebro do sistema de autorização, avaliando cada solicitação contra políticas ou papéis predefinidos e determinando se deve permitir ou negar a solicitação. Há alguns benefícios claros com um PDP centralizado:

Consistência: Cada solicitação é avaliada contra o mesmo conjunto de políticas, garantindo uma tomada de decisão consistente em todos os nossos sistemas.
Gerenciamento e Conformidade: Com todas as políticas gerenciadas em um único lugar, atualizações e auditorias tornam-se fáceis. Registrar decisões para fins de auditoria é importante para algumas conformidades regulatórias.

No entanto, uma desvantagem seria o aumento da latência. Encaminhar cada solicitação de autorização através de um único PDP pode desacelerar as coisas, especialmente em um sistema distribuído.

Usando Amazon Verified Permissions (AVP) um PDP centralizado é necessário.

PEPs Distribuídos

Pontos de Aplicação de Política (PEPs) são onde as decisões de autorização tomadas pelo PDP são aplicadas. Esses pontos estão distribuídos por todo o nosso sistema. Nosso Autorizador baseado em Lambda é um exemplo de PEP. A porta frontal dos nossos microservices também pode atuar como PEP e chamar nosso PDP.

Temos alguns benefícios ao executar nossos PEPs de forma distribuída.

Redução de Latência: Ao colocar PEPs próximos de onde as decisões precisam ser aplicadas, podemos reduzir a latência, com uma estratégia de cache isso pode ser reduzido ainda mais.
Escalabilidade: À medida que seu sistema cresce, PEPs distribuídos garantem que nenhum ponto único se torne um gargalo, especialmente com cache habilitado.

Visão Geral da Arquitetura

Em nossa nova arquitetura de autorização, introduziremos um novo serviço de autorização, que será nosso PDP central. A lógica de autorização será movida de nosso Autorizador Lambda anterior para o PDP, em vez disso, nosso Autorizador Lambda agora chamará o PDP que retornará a decisão de acesso.

Imagem mostrando visão geral da criação de tenant

Criar Serviço de Autorização (PDP)

A primeira coisa que precisamos fazer é criar nosso Serviço de Autorização (PDP), como mostrado, isso será um API Gateway e uma função Lambda. Uma abordagem interessante que poderíamos usar é remover o API Gateway e apenas usar URLs de Função Lambda. No entanto, para uma extensão mais fácil no futuro, decidi usar um API Gateway.


AWSTemplateFormatVersion: "2010-09-09"
Transform: "AWS::Serverless-2016-10-31"
Description: Connected BBQ Application Tenant Service
Parameters:
  ApplicationName:
    Type: String
    Description: Name of owning application
    Default: bbq-iot
  UserPoolStackName:
    Type: String
    Description: The name of the Stack with the Cognito User Pool

Globals:
  Function:
    Timeout: 30
    MemorySize: 2048
    Runtime: python3.12

Resources:
  LambdaPDPFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: Lambda/AuthZ
      Handler: authz.handler
      Environment:
        Variables:
          JWKS_URL:
            Fn::ImportValue: !Sub ${UserPoolStackName}:jwks-url
          AUDIENCE:
            Fn::ImportValue: !Sub ${UserPoolStackName}:app-audience
      Events:
        AuthZ:
          Type: Api
          Properties:
            Path: /authz
            Method: post
            RestApiId: !Ref PDPApi
            Auth:
              AuthorizationType: AWS_IAM

  PDPApi:
    Type: AWS::Serverless::Api
    Properties:
      Name: !Sub ${ApplicationName}-pdp-api
      StageName: prod
      EndpointConfiguration: REGIONAL
      Cors:
        AllowMethods: "'GET,PUT,POST,DELETE,OPTIONS'"
        AllowHeaders: "'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'"
        AllowOrigin: "'*'"
      Auth:
        DefaultAuthorizer: AWS_IAM

Outputs:
  PDPApiID:
    Value: !Ref PDPApi
    Description: The ID of the PDP API
    Export:
      Name: !Sub ${AWS::StackName}:pdp-api-id
  

Nossa API usará AWS IAM para autorização máquina-2-máquina, assim como nossa API de admin que criamos na parte anterior.

Em seguida, movemos nossa lógica de AuthZ de nosso Autorizador Lambda para nossa nova implementação de PDP. Vamos lidar com ambas as autorizações para acesso de usuário e tenant e introduzir dois novos recursos tenant e tenantUser.


import os
import json
import jwt
from jwt import PyJWKClient


def handler(event, context):
    data = json.loads(event["body"])
    jwt_token = data["jwt_token"]

    try:
        jwks_url = os.environ["JWKS_URL"]

        jwks_client = PyJWKClient(jwks_url)
        signing_key = jwks_client.get_signing_key_from_jwt(jwt_token)

        decoded_token = jwt.decode(
            jwt_token,
            signing_key.key,
            algorithms=["RS256"],
            audience=os.environ["AUDIENCE"],
        )

		# Verificar recurso e tomar uma decisão de AuthZ
        if data["resource"] == "tenant":
            tenant_id = data["tenant_id"]
            token_tenant_id = decoded_token.get("tenant")

            if token_tenant_id == tenant_id:
                response_body = generate_access(
                    "Allow", data["action"], data["resource"]
                )
                return {
                    "statusCode": 200,
                    "body": json.dumps(response_body),
                    "headers": {"Content-Type": "application/json"},
                }
        elif data["resource"] == "tenantUser":
            user_id = data["user_id"]
            token_user_id = decoded_token.get("cognito:username")

            if token_user_id == user_id:
                response_body = generate_access(
                    "Allow", data["action"], data["resource"]
                )
                return {
                    "statusCode": 200,
                    "body": json.dumps(response_body),
                    "headers": {"Content-Type": "application/json"},
                }

    except Exception as e:
        print(f"Authorization error: {str(e)}")

    # Gerar uma resposta padrão que nega acesso
    response_body = generate_access("Deny", data["action"], data["resource"])

    return {
        "statusCode": 403,
        "body": json.dumps(response_body),
        "headers": {"Content-Type": "application/json"},
    }


def generate_access(effect, action, resource):
    auth_response = {
        "effect": effect,
        "action": action,
        "resource": resource,
    }
    return auth_response

Em seguida, é claro, precisamos atualizar o Autorizador Lambda para chamar nosso PDP para autorização.


api_endpoint = os.environ.get("PDP_AUTHZ_API_ENDPOINT")

def handler(event, context):
    token = event["headers"].get("authorization", "")

    if not token:
        raise Exception("Unauthorized")

    token = token.replace("Bearer ", "")

    try:
        
		......

        path_tenant_id = event["pathParameters"]["tenantId"]

        session = boto3.Session()
        credentials = session.get_credentials().get_frozen_credentials()

        region = os.environ["AWS_REGION"]
        auth = AWS4Auth(
            credentials.access_key,
            credentials.secret_key,
            region,
            "execute-api",
            session_token=credentials.token,
        )

        data = {
            "jwt_token": token,
            "resource": "tenant",
            "action": "read",
            "resource_path": event["path"],
            "tenant_id": event["pathParameters"]["tenantId"],
        }
        headers = {"Content-type": "application/json"}
        response = requests.post(
            api_endpoint + "/authz", data=json.dumps(data), headers=headers, auth=auth
        )

		......

    except Exception as e:
        print(f"Authorization error: {str(e)}")

Resumo

Com essas mudanças, movemos nossa autorização para um PDP centralizado com uma configuração de PEP distribuído. Isso cria uma boa base para continuarmos evoluindo nossa AuthZ. Em posts futuros, introduziremos um RBAC (Role Based Access Control) e ABAC (Attribute Based Access Control), e também mudaremos para usar o Amazon Verified permissions.

Obtenha o código

A configuração completa com todo o código está disponível no Serverless Handbook

Palavras Finais

Neste post, examinamos a arquitetura de um sistema de autorização centralizado usando um PDP e PEPs distribuídos. Também destacamos as diferenças entre Autenticação (AuthN) e Autorização (AuthZ).

Nos próximos posts, começaremos a ver o onboarding de dispositivos e dados, fique atento!

Confira meu Serverless Handbook para o código da solução construída nesta série de posts.

Não se esqueça de me seguir no LinkedIn e X para mais conteúdo, e leia o restante dos meus Blogs

Como Werner diz! Agora Vá Construir!

If this saved you an afternoon, you can buy me a coffee.