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

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.

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!