Construyendo un BBQ conectado serverless como SaaS - Parte 4 - AuthZ

Questo file e stato tradotto automaticamente dall'IA, potrebbero verificarsi errori
Ya hemos abordado la aislación de datos y la seguridad a nivel de fila en un entorno SaaS multitenante. Ahora es el momento de ver cómo manejar la autorización, el proceso de controlar el acceso a los datos y la funcionalidad según quién es el usuario y qué está autorizado para hacer.
En esta publicación, exploraremos la arquitectura de un Punto de Decisión de Política (PDP) centralizado con Puntos de Aplicación de Política (PEP) distribuidos para garantizar una autorización consistente y segura en toda su plataforma SaaS. También desglosaremos las diferencias clave entre Autenticación (AuthN) y Autorización (AuthZ).
Si aún no la ha revisado, aquí están la primera parte, la segunda parte y la tercera parte
Autenticación (AuthN) vs. Autorización (AuthZ)
Antes de entrar en la construcción de la arquitectura, necesitamos establecer un entendimiento común. Es crucial distinguir entre Autenticación y Autorización, dos términos que a menudo se confunden y que he tenido que explicar en muchas ocasiones, pero que cumplen propósitos muy diferentes.
Autenticación (AuthN)
La autenticación se trata de verificar la identidad. Responde a la pregunta, ¿Quién eres? Cuando un usuario inicia sesión en nuestra aplicación SaaS, la autenticación asegura que sean quienes dicen ser. Esto podría implicar algo tan simple como un nombre de usuario y contraseña o una autenticación multifactor (MFA) más compleja.
Estamos utilizando Amazon Cognito User Pools para nuestra autenticación en esta serie.
Autorización (AuthZ)
Una vez que la identidad del usuario está autenticada, entra en juego la autorización. Este proceso responde a la pregunta, ¿Qué puedes hacer? La autorización determina qué recursos, datos y acciones está permitido acceder a un usuario según sus roles y permisos.
En un entorno multitenante, hacer bien la autorización es importante. Necesitamos asegurarnos de que los usuarios solo puedan acceder a los datos y la funcionalidad relevante para su inquilino y rol. Aquí es donde entra en juego un sistema de autorización robusto y escalable.
PDP Centralizado y PEP Distribuidos: La Espina Dorsal de la Autorización
Ahora que hemos aclarado la diferencia entre AuthN y AuthZ, centrémonos en la construcción de un sistema de autorización utilizando un PDP centralizado y PEP distribuidos.
PDP Centralizado
Un Punto de Decisión de Política (PDP) es donde se toman todas las decisiones de autorización. Es el cerebro del sistema de autorización, evaluando cada solicitud contra políticas o roles predefinidos y determinando si permitir o denegar la solicitud. Hay algunos beneficios claros con un PDP centralizado:
Consistencia: Cada solicitud se evalúa contra el mismo conjunto de políticas, asegurando una toma de decisiones consistente en todos nuestros sistemas.
Gestión y Cumplimiento: Con todas las políticas gestionadas en un solo lugar, las actualizaciones y las auditorías son más fáciles. Registrar decisiones para fines de auditoría es importante para cumplir con algunas normativas.
Sin embargo, un inconveniente sería el aumento de la latencia. Enrutar cada solicitud de autorización a través de un solo PDP puede ralentizar las cosas, especialmente en un sistema distribuido.
Usando Amazon Verified Permissions (AVP) se requiere un PDP centralizado.
PEP Distribuidos
Los Puntos de Aplicación de Política (PEP) son donde se hacen cumplir las decisiones de autorización tomadas por el PDP. Estos puntos están distribuidos por todo nuestro sistema. Nuestro Autorizador basado en Lambda es un ejemplo de PEP. La puerta principal de nuestros microservicios también puede actuar como PEP y llamar a nuestro PDP.
Tenemos algunos beneficios al ejecutar nuestros PEP de manera distribuida.
Reducción de la Latencia: Al colocar los PEP cerca de donde se deben aplicar las decisiones, podemos reducir la latencia, y con una estrategia de caché esto se puede reducir aún más.
Escalabilidad: A medida que su sistema crece, los PEP distribuidos aseguran que ningún punto único se convierta en un cuello de botella, especialmente con el caché habilitado.
Visión General de la Arquitectura
En nuestra nueva arquitectura de autorización introduciremos un nuevo servicio de autorización, que será nuestro PDP central. La lógica de autorización se moverá de nuestro Autorizador Lambda anterior al PDP, en su lugar, nuestro Autorizador Lambda ahora llamará al PDP que devolverá la decisión de acceso.

Crear el servicio de autorización (PDP)
Lo primero que debemos hacer es crear nuestro Servicio de Autorización (PDP), como se muestra, este será un API Gateway y una función Lambda. Un enfoque interesante que podríamos usar es eliminar el API Gateway y solo usar URL de Función Lambda. Sin embargo, para una mayor extensibilidad posterior, decidí usar un 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
Nuestro API usará AWS IAM para la autorización máquina-a-máquina, al igual que nuestra API de administración que creamos en nuestra parte anterior.
A continuación, movemos nuestra lógica de AuthZ de nuestro Autorizador Lambda a nuestra nueva implementación de PDP. Manejaremos tanto la autorización para el acceso de usuarios como de inquilinos e introduciremos dos nuevos recursos tenant y 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 el recurso y tomar una decisión 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)}")
# Generar una respuesta predeterminada que deniega el acceso
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
A continuación, por supuesto, necesitamos actualizar el Autorizador Lambda para llamar a nuestro PDP para la autorización.
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)}")Resumen
Con esos cambios, hemos movido nuestra autorización a un PDP centralizado con una configuración de PEP distribuido. Esto crea una buena base para seguir evolucionando nuestro AuthZ. En publicaciones futuras introduciremos un RBAC (Control de Acceso Basado en Roles) y ABAC (Control de Acceso Basado en Atributos), también pasaremos a usar permisos verificados de Amazon.
Obtenga el código
La configuración completa con todo el código está disponible en Serverless Handbook
Palabras Finales
En esta publicación, hemos visto la arquitectura de un sistema de autorización centralizado usando un PDP y PEP distribuidos. También hemos destacado las diferencias entre Autenticación (AuthN) y Autorización (AuthZ).
En las próximas publicaciones comenzaremos a ver el onboarding de dispositivos y datos, ¡manténgase atento!
Consulte mi Manual Serverless para obtener el código de la solución construida en esta serie de publicaciones.
No olvide seguirme en LinkedIn y X para más contenido, y lea el resto de mis Blogs
Como dice Werner! ¡Ahora ve a construir!