aws, authz, serverless

PEP y PDP para Autorización Segura con Cognito

2025-01-30
This post cover image
aws cloud serverless AuthZ

Este archivo ha sido traducido automaticamente por IA, pueden ocurrir errores

En una de mis publicaciones anteriores Construyendo un BBQ conectado sin servidor como SaaS - Parte 4 - AuthZ mencioné el tema de Autenticación y Autorización con PEP distribuidos (Puntos de Aplicación de Política) y un PDP centralizado (Punto de Decisión de Política). En esta publicación profundizaré un poco más y expandiré esa configuración. Exploraré cómo estos conceptos funcionan en la práctica, los beneficios que ofrecen y cómo podemos aprovecharlos en nuestra arquitectura sin servidor usando AWS Lambda, API Gateway y Cognito User Pools.

Además, hablaré sobre el modelo de Control de Acceso Basado en Roles (RBAC), cómo implementarlo usando Grupos de Cognito y DynamoDB, y cómo el caché puede mejorar el rendimiento de nuestro sistema de autorización.

Toda la configuración, con instrucciones detalladas de despliegue y todo el código, se puede encontrar en Serverless Handbook PEP y PDP

Comencemos con un breve repaso.

Autenticación (AuthN) vs. Autorización (AuthZ)

Es crucial distinguir entre Autenticación y Autorización, dos términos que a menudo se mezclan y que he tenido que explicar en muchas ocasiones, pero que sirven 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, la autenticación asegura que sean quien dicen ser. Esto podría involucrar algo tan simple como un nombre de usuario y contraseña o una autenticación multifactor (MFA) más compleja.

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 al usuario basándose en sus roles y permisos.

¿Qué son PEP y PDP?

Antes de profundizar en cómo implementarlos en AWS, es esencial entender los roles que PEP y PDP juegan en la autorización.

PEP (Punto de Aplicación de Política)

En términos simples, el PEP es el guardián. Son los puntos en nuestro sistema donde se hacen cumplir las decisiones de control de acceso. Cuando un usuario intenta acceder a un recurso protegido, el PEP es el componente responsable de verificar si la solicitud está permitida o denegada basándose en los permisos del usuario.

En nuestro caso, el Autorizador Lambda en API Gateway actúa como el PEP. El Autorizador Lambda intercepta cada solicitud API entrante, valida el token JWT (generalmente del Cognito User Pool o cualquier proveedor de identidad), y envía la información del usuario (reclamaciones) al PDP para la evaluación de autorización.

El PEP asegura que el JWT es válido, verifica su caducidad, comprueba su firma y valida las reclamaciones (como aud, iss, y sub). Luego pasa las reclamaciones al PDP para una decisión final sobre si el usuario está autorizado para acceder al recurso solicitado.

PDP (Punto de Decisión de Política)

El PDP es donde reside la lógica de autorización. Una vez que el PEP verifica el JWT y asegura que el token es válido, el PDP determina si el usuario está permitido para acceder al recurso solicitado basándose en sus roles, permisos o políticas.

El PDP es una implementación separada, un microservicio independiente. En nuestro caso, una función Lambda, que realiza la decisión de autorización real. Verifica los roles del usuario, que están almacenados en la reclamación groups en el JWT (de Cognito), y los compara con los permisos requeridos para acceder a un recurso específico, almacenados en un almacén de datos. En nuestro caso usaremos DynamoDB.

El PDP valida si el usuario tiene los permisos necesarios (como Admin, User, o Manager) para acceder al recurso (por ejemplo, GET /admin, POST /profile). El PDP también puede incorporar lógica de negocio adicional, como verificar el acceso basado en el tiempo o geolocalización.

Beneficios de usar PEP y PDP en la Autorización

Implementar PEP distribuidos y PDP centralizado ofrece varios beneficios, especialmente a medida que nuestras aplicaciones escalan.

Separación de Preocupaciones Al dividir las preocupaciones de aplicación (PEP) y decisión (PDP), obtenemos código más limpio y mantenible. El Autorizador Lambda (PEP) se enfoca puramente en la validación y aplicación. Mientras que el PDP está dedicado a la evaluación de políticas.

Reducción de Latencia: Al colocar los PEP cerca de donde se deben tomar las decisiones, podemos reducir la latencia, con una estrategia de caché esto se puede reducir aún más.

Gestión: Con un PDP centralizado, toda nuestra lógica de autorización está centralizada en una ubicación. Esto hace que sea más fácil gestionar y actualizar políticas a medida que evolucionan nuestros requisitos. Ya sea modificando roles o agregando nuevos conjuntos de permisos, tener un PDP central reduce la sobrecarga de actualizar políticas en múltiples lugares.

Consistencia y Cumplimiento: Cada solicitud se evalúa contra el mismo conjunto de políticas, asegurando una toma de decisiones consistente en todo nuestro sistema.

Escalabilidad: Ambos componentes PEP y PDP se escalan independientemente según la demanda. Si nuestro sistema necesita manejar un mayor volumen de solicitudes, API Gateway y Lambda pueden escalar automáticamente. Además, el PDP puede optimizarse para el rendimiento implementando caché.

Flexibilidad: Un PDP nos permite adaptar el modelo de autorización a nuestras necesidades. Si nuestros requisitos cambian (por ejemplo, pasar al control de acceso basado en atributos (ABAC) o introducir un sistema de permisos más granular), podemos modificar fácilmente el PDP para acomodar estos cambios sin afectar otras partes del sistema.

Usando PEP y PDP en AWS con Arquitectura Sin Servidor

En AWS, la integración de PEP y PDP encaja perfectamente con componentes sin servidor como Lambda y API Gateway.

PEP - Autorizador Lambda de API Gateway

Cuando un cliente envía una solicitud a nuestro extremo de API Gateway, el Autorizador Lambda (PEP) intercepta la solicitud antes de que llegue a nuestro servicio de backend. Nuestra implementación realizará varios pasos clave.

Validación de JWT: Decodifica el JWT, valida la firma y verifica si el token ha caducado.

Envío de Reclamaciones: Después de verificar el token, el Autorizador Lambda envía las reclamaciones (como sub, groups, y role) al PDP para futuras verificaciones de autorización. En nuestra solución en realidad enviaremos el token JWT completo.

Para reducir el número de llamadas a nuestro PEP y también al PDP podemos utilizar el caché de autorización que existe en API Gateway.

PDP - Función Lambda de lógica de autorización

El PDP está implementado como una función Lambda separada, en nuestro caso, y recibirá el token JWT completo, o las reclamaciones, para realizar la lógica de autorización, que incluirá varios pasos.

  • Verificar el rol del usuario (usando la reclamación groups de Cognito).
  • Consultar una tabla de DynamoDB que contiene asignaciones de rol a permisos (por ejemplo, qué roles tienen acceso a qué extremos de API).
  • Evalúa si el rol del usuario coincide con los permisos requeridos para el recurso o extremo de API solicitado.

Token de ID vs Token de Acceso

Al implementar el flujo de trabajo de PEP y PDP, es esencial entender la diferencia entre Tokens de ID y Tokens de Acceso, ya que ambos se usan a menudo en flujos de trabajo de autorización.

Token de ID

El Token de ID se usa principalmente para autenticación y contiene información sobre quién es el usuario. Contiene reclamaciones sobre la identidad del usuario autenticado, como nombre, correo electrónico y número de teléfono.

Token de Acceso

El Token de Acceso se usa para otorgar al usuario acceso a recursos protegidos, autorización. El Token de Acceso contiene información sobre los permisos del usuario, como a qué recursos está permitido acceder y los alcances que se le han concedido, que definen lo que el usuario puede hacer (por ejemplo, read:profile, write:profile). El token de acceso no incluye la reclamación aud.

Personalización de tokens en Cognito

Con el activador Lambda de generación de pre-token podíamos antes solo personalizar el Token de ID, por lo tanto a menudo se usaba también para autorización. Con la introducción del nuevo evento V2 en Cognito User Pools podemos personalizar tanto el Token de ID como el Token de Acceso.

Implementando PEP y PDP

Con esta introducción completada, profundicemos en la implementación de un PEP y PDP con RBAC. Nuestro PEP será el Autorizador Lambda en API Gateway y nuestro PDP será una función Lambda separada. El PDP usará Grupos de Cognito y DynamoDB para la lógica de autorización RBAC.

Visión general de la arquitectura

Como recordatorio, todo el código y la arquitectura se pueden encontrar en Serverless Handbook PEP y PDP

En esta solución implementaremos nuestro PEP usando el Autorizador Lambda en API Gateway. El PDP en este caso también se implementará usando una función Lambda. Asignaremos a los usuarios un Rol usando Grupos de Cognito y mantendremos una asignación Rol - Permiso en DynamoDB.

Imagen mostrando la visión general de la arquitectura

Para entender mejor el flujo durante un acceso a la API.

Imagen mostrando el flujo de llamadas

Como se ve, no usaremos un API Gateway para nuestro PDP. En su lugar, nuestro PEP invocará la función Lambda del PDP. Hay pros y contras con este enfoque, por supuesto. En el lado positivo tenemos menor latencia, una invocación directa de Lambda es a menudo más rápida que una llamada a una API. Menor costo ya que no tenemos que pagar por la invocación de API Gateway. En el lado negativo, creamos un acoplamiento más estrecho y cambiar la implementación del PDP podría ser más difícil. Necesitaríamos implementar un caché separado en el PDP, usando un API Gateway podríamos confiar en el caché de API Gateway.

Sin embargo, el enfoque que elijas debe ser caso por caso, no hay una regla de oro sobre cómo implementarlo exactamente.

Desplegar autenticación y Cognito

Lo primero que haremos es desplegar y configurar Cognito y los recursos necesarios para el inicio de sesión. Configuraremos el UserPool de Cognito, el inicio de sesión administrado y un sitio web simple que manejará los callbacks de Cognito y mostrará nuestros tokens JWT. Por simplicidad será solo una página HTML estática desde CloudFront y algunas funciones Lambda@Edge. Usaré la configuración que he descrito en esta publicación de blog, así que para una inmersión profunda te recomiendo que la leas.

Así que como primer paso desplegamos Lambda@Edge, la distribución de CloudFront y el certificado SSL desde Serverless Handbook PEP y PDP

Luego, despleguemos y configuremos Cognito. Crearemos el UserPool, un cliente, estilo de inicio de sesión, etc.

AWSTemplateFormatVersion: "2010-09-09"
Transform: "AWS::Serverless-2016-10-31"
Description: Crea el User Pool y Cliente usados para Autenticación
Parámetros:
  ApplicationName:
    Tipo: String
    Descripción: La aplicación que posee esta configuración.
  DomainName:
    Tipo: String
    Descripción: El nombre de dominio para usar en cloudfront
  HostedAuthDomainPrefix:
    Tipo: String
    Descripción: El prefijo de dominio para usar para la UI alojada del UserPool <HostedAuthDomainPrefix>.auth.[región].amazoncognito.com

Recursos:
  UserPool:
    Tipo: AWS::Cognito::UserPool
    Propiedades:
      Configuración de nombre de usuario:
        CaseSensitive: false
      Atributos verificados automáticamente:
        - email
      Nombre del UserPool: !Sub ${ApplicationName}-user-pool
      Esquema:
        - Nombre: email
          Tipo de atributo de datos: String
          Mutables: false
          Requerido: true
        - Nombre: name
          Tipo de atributo de datos: String
          Mutables: true
          Requerido: true

  UserPoolClient:
    Tipo: AWS::Cognito::UserPoolClient
    Propiedades:
      UserPoolId: !Ref UserPool
      GenerarSecreto: Verdadero
      AllowedOAuthFlowsUserPoolClient: true
      CallbackURLs:
        - !Sub https://${DomainName}/signin
      AllowedOAuthFlows:
        - code
        - implicit
      AllowedOAuthScopes:
        - phone
        - email
        - openid
        - profile
      SupportedIdentityProviders:
        - COGNITO

  HostedUserPoolDomain:
    Tipo: AWS::Cognito::UserPoolDomain
    Propiedades:
      Dominio: !Ref HostedAuthDomainPrefix
      Versión de inicio de sesión administrado: 2
      UserPoolId: !Ref UserPool

  ManagedLoginStyle:
    Tipo: AWS::Cognito::ManagedLoginBranding
    Propiedades:
      ClientId: !Ref UserPoolClient
      UserPoolId: !Ref UserPool
      UseCognitoProvidedValues: true

  UserPoolIdParameter:
    Tipo: AWS::SSM::Parameter
    Propiedades:
      Nombre: !Sub /${ApplicationName}/userPoolId
      Tipo: String
      Valor: !Ref UserPool
      Descripción: Parámetro SSM para el ID del User Pool
      Etiquetas:
        ApplicationName: !Ref ApplicationName

  UserPoolHostedUiParameter:
    Tipo: AWS::SSM::Parameter
    Propiedades:
      Nombre: !Sub /${ApplicationName}/userPoolHostedUi
      Tipo: String
      Valor: !Sub https://${HostedAuthDomainPrefix}.auth.${AWS::Region}.amazoncognito.com/login?client_id=${UserPoolClient}&response_type=code&scope=email+openid+phone+profile&redirect_uri=https://${DomainName}/signin
      Descripción: Parámetro SSM para la UI alojada del User Pool
      Etiquetas:
        ApplicationName: !Ref ApplicationName

Salidas:
  CognitoUserPoolJwksUri:
    Valor: !Sub https://cognito-idp.${AWS::Region}.amazonaws.com/${UserPool}/.well-known/jwks.json
    Descripción: La URI del jwks del UserPool
    Exportar:
      Nombre: !Sub ${AWS::StackName}:jwks-url
  CognitoUserPoolID:
    Valor: !Ref UserPool
    Descripción: El ID del UserPool
  CognitoAppClientID:
    Valor: !Ref UserPoolClient
    Descripción: El cliente de la aplicación
    Exportar:
      Nombre: !Sub ${AWS::StackName}:app-audience
  CognitoUrl:
    Descripción: La url
    Valor: !GetAtt UserPool.ProviderURL
  CognitoHostedUI:
    Valor: !Sub https://${HostedAuthDomainPrefix}.auth.${AWS::Region}.amazoncognito.com/login?client_id=${UserPoolClient}&response_type=code&scope=email+openid+phone+profile&redirect_uri=https://${DomainName}/signin
    Descripción: La URL de la UI alojada

Con este despliegue hecho podemos pasar a la Consola y crear grupos a los que se pueden agregar usuarios. Crearé tres grupos, Admin, Developer y Test. Haz clic en Crear Grupo y dale un nombre. El nombre del grupo representaría el Rol que tendrá el usuario y determinará qué permisos obtendrá, más sobre esa configuración más adelante.

Imagen mostrando grupos de cognito

Luego podemos crear algunos usuarios y asignarlos a uno de los grupos.

Para probar esta configuración podemos navegar a la página web desplegada con la distribución de CloudFront e inspeccionar los tokens JWT, cookies.

Imagen mostrando cookies

Si copiamos el token de acceso y lo decodificamos, uso jwt.io, podemos ver que mi usuario tiene la reclamación cognito:groups que nuestro PEP y PDP usarán más tarde para los permisos.

Configurar y desplegar PDP

Luego podemos desplegar nuestro servicio de Autorización, nuestro PDP, responsable de tomar decisiones de permisos.

La lógica se implementará en una función Lambda y para gestionar los permisos basados en roles, crearemos una tabla DynamoDB que almacena permisos para cada rol. Cada permiso define qué recursos puede acceder el usuario, esto podría ser un extremo de API específico y el método HTTP, pero por supuesto no limitado a eso. Modelaremos los datos de la tabla

PK (Clave de partición): El Rol (por ejemplo, Admin, User). SK (Clave de ordenación): El recurso, por ejemplo extremo y Método, por ejemplo GET /unicorn. Acción: La acción, por ejemplo GET, PUT, WRITE, READ, LIST etc Recurso: El Recurso, por ejemplo el extremo /unicorn Efecto: El Efecto, Permitir o Denegar Descripción: Una descripción del permiso.

PKSKAcciónRecursoEfectoDescripción
AdminGET /unicornGET/unicornPermitirAdmin puede acceder a todos los unicornios
TestPOST /unicornPOST/unicornPermitirTest puede publicar en unicornios
DeveloperDELETE /unicornDELETE/unicornDenegarManager no puede eliminar un unicornio

Esto nos permite buscar eficientemente permisos para cada rol usando una simple consulta de DynamoDB.

AWSTemplateFormatVersion: "2010-09-09"
Transform: "AWS::Serverless-2016-10-31"
Descripción: Aplicación de BBQ conectado Servicio de inquilinos
Parámetros:
  ApplicationName:
    Tipo: String
    Descripción: Nombre de la aplicación propietaria
  UserManagementStackName:
    Tipo: String
    Descripción: El nombre de la pila que contiene la parte de gestión de usuarios, por ejemplo el Cognito UserPool

Globals:
  Función:
    Tiempo de espera: 30
    Tamaño de memoria: 2048
    Arquitecturas:
      - arm64
    Tiempo de ejecución: python3.12

Recursos:
  PermissionsTable:
    Tipo: AWS::DynamoDB::Table
    Propiedades:
      Nombre de la tabla:
        Fn::Sub: ${ApplicationName}-pdp-role-permission-map
      Modo de facturación: PAY_PER_REQUEST
      Definiciones de atributos:
      - Nombre de atributo: PK
        Tipo de atributo: S
      - Nombre de atributo: SK
        Tipo de atributo: S
      Esquema de clave:
      - Nombre de atributo: PK
        Tipo de clave: HASH
      - Nombre de atributo: SK
        Tipo de clave: RANGE

  LambdaPDPFunction:
    Tipo: AWS::Serverless::Function
    Propiedades:
      CodeUri: Lambda/AuthZ
      Handler: authz.handler
      Políticas:
        - DynamoDBReadPolicy:
            Tabla: !Ref PermissionsTable
      Entorno:
        Variables:
          JWKS_URL:
            Fn::ImportValue: !Sub ${UserManagementStackName}:jwks-url
          AUDIENCE:
            Fn::ImportValue: !Sub ${UserManagementStackName}:app-audience
          PERMISSIONS_TABLE:
            !Ref PermissionsTable

Salidas:
  PDPLambdaArn:
    Valor: !GetAtt LambdaPDPFunction.Arn
    Descripción: El ARN de la función Lambda del PDP
    Exportar:
      Nombre: !Sub ${AWS::StackName}:pdp-lambda-arn
  PDPLambdaName:
    Valor: !Ref LambdaPDPFunction
    Descripción: El Nombre de la función Lambda del PDP
    Exportar:
      Nombre: !Sub ${AWS::StackName}:pdp-lambda-name

Lógica de autorización de roles

El PDP Lambda decodificará el JWT, recuperará el rol de la reclamación cognito:groups y consultará la tabla DynamoDB para verificar si el rol tiene permiso para acceder al recurso solicitado.

import os
import json
import jwt
import boto3
from jwt import PyJWKClient
from botocore.exceptions import ClientError

dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table(os.environ["PERMISSIONS_TABLE"])
JWKS_URL = os.environ["JWKS_URL"]
AUDIENCE = os.environ["AUDIENCE"]


def handler(event, context):
  data = event
    jwt_token = data["jwt_token"]
    resource = data["resource"]
    action = data["action"]

    return check_authorization(jwt_token, action, resource)


def check_authorization(jwt_token, action, resource):
    try:
        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=AUDIENCE,
        )

        role = (
            decoded_token["cognito:groups"][0]
            if "cognito:groups" in decoded_token
            else None
        )

        if not role:
            raise Exception("No autorizado: Rol no encontrado en el token")

        if validate_permission(role, action, resource):
            response_body = generate_access(
                decoded_token["sub"], "Permitir", action, resource
            )

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

    except Exception as e:
        print(f"Error de autorización: {str(e)}")

    response_body = generate_access(decoded_token["sub"], "Denegar", action, resource)

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


def validate_permission(role, action, resource):
    print(f"validate_permission Rol: {role}, Acción: {action}, Recurso: {resource}")
    try:
        response = table.query(
            KeyConditionExpression="PK = :role AND SK = :endpoint",
            ExpressionAttributeValues={
                ":role": role,
                ":endpoint": f"{action} {resource}",
            },
        )
        if response["Items"] and response["Items"][0]["Efecto"] == "Permitir":
            return True
        else:
            return False
    except ClientError as e:
        print(f"Error consultando DynamoDB: {e}")
        return False


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

Desplegar API y PEP

Ahora podemos desplegar nuestra API y PEP, Autorizador Lambda.

AWSTemplateFormatVersion: "2010-09-09"
Transform: "AWS::Serverless-2016-10-31"
Descripción: Crea la API para la gestión de certificados de autoservicio
Parámetros:
  ApplicationName:
    Tipo: String
    Descripción: Nombre de la aplicación propietaria
  UserManagementStackName:
    Tipo: String
    Descripción: El nombre de la pila que contiene la parte de gestión de usuarios, por ejemplo el Cognito UserPool
  PDPStackName:
    Tipo: String
    Descripción: El nombre de la pila que contiene el servicio PDP

Globals:
  Función:
    Tiempo de espera: 30
    Tamaño de memoria: 2048
    Tiempo de ejecución: python3.12

Recursos:
  LambdaGetUnicorn:
    Tipo: AWS::Serverless::Function
    Propiedades:
      CodeUri: Lambda/API/GetUnicorn
      Handler: handler.handler
      Eventos:
        GetUnicorns:
          Tipo: Api
          Propiedades:
            Ruta: /unicorn
            Método: get
            RestApiId: !Ref UnicornApi

  UnicornApi:
    Tipo: AWS::Serverless::Api
    Propiedades:
      Descripción: API para crear y gestionar Unicornios
      Nombre: !Sub ${ApplicationName}-api
      Nombre de etapa: prod
      Versión de OpenApi: '3.0.1'
      Siempre desplegar: verdadero
      Configuración de extremo: REGIONAL
      Cors:
        AllowMethods: "'GET,PUT,POST,DELETE,OPTIONS'"
        AllowHeaders: "'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'"
        AllowOrigin: "'*'"
      Auth:
        AddDefaultAuthorizerToCorsPreflight: false
        Authorizers:
          LambdaRequestAuthorizer:
            FunctionArn: !GetAtt LambdaApiAuthorizer.Arn
            FunctionPayloadType: REQUEST
            Identidad: 
              Encabezados: 
                - Autorización
              ReauthorizeEvery: 600
        Autorizador predeterminado: LambdaRequestAuthorizer

  LambdaApiAuthorizer:
    Tipo: AWS::Serverless::Function
    Propiedades:
      CodeUri: Lambda/Authorizer/
      Handler: auth.handler
      Políticas:
        - LambdaInvokePolicy:
            FunctionName: 
              Fn::ImportValue: !Sub ${PDPStackName}:pdp-lambda-name
      Entorno:
        Variables:
          JWKS_URL:
            Fn::ImportValue: !Sub ${UserManagementStackName}:jwks-url
          AUDIENCE:
            Fn::ImportValue: !Sub ${UserManagementStackName}:app-audience
          PDP_AUTHZ_ENDPOINT: 
            Fn::ImportValue: !Sub ${PDPStackName}:pdp-lambda-name

Establecemos nuestro PEP como el autorizador predeterminado para que se agregue a cada recurso y método. Para reducir el número de llamadas a nuestro PDP, se utiliza el caché de autorización en API Gateway con un TTL de 600 segundos.

Lógica de autorización PEP

El Autorizador Lambda PEP decodificará el JWT, verificará su validez y luego llamará al PDP para una decisión final de permiso.

import os
import json
import jwt
import boto3
from jwt import PyJWKClient

lambda_client = boto3.client("lambda")


def handler(event, context):
    print(f"Evento: {json.dumps(event)}")
    token = event["headers"].get("autorización", "")
    path = event["path"]
    method = event["httpMethod"]

    if not token:
        raise Exception("No autorizado")

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

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

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

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

        data = {
            "jwt_token": token,
            "recurso": path,
            "acción": method,
        }
        
        response = lambda_client.invoke(
            FunctionName=os.environ["PDP_AUTHZ_ENDPOINT"],
            InvocationType="RequestResponse",
            Payload=json.dumps(data),
        )

        response_payload = json.loads(response["Payload"].read())
        body = json.loads(response_payload["body"])
        effect = body["effect"]

        return generate_policy(
            decoded_token["sub"], effect, event["methodArn"], decoded_token
        )

    except Exception as e:
        print(f"Error de autorización: {str(e)}")

    return generate_policy(
        decoded_token["sub"], "Denegar", event["methodArn"], decoded_token
    )


def generate_policy(principal_id, effect, resource):
    auth_response = {
        "principalId": principal_id,
        "policyDocument": {
            "Versión": "2012-10-17",
            "Declaración": [
                {"Acción": "execute-api:Invoke", "Efecto": effect, "Recurso": resource}
            ],
        },
    }
    return auth_response

Importancia del caché

El caché es importante para optimizar nuestro flujo de autorización. Al reducir las llamadas al PDP y acelerar la toma de decisiones, el caché ayuda a mejorar el rendimiento general, la escalabilidad y la eficiencia de costos de nuestra aplicación.

Reducción de la latencia: Al almacenar en caché los datos de roles y permisos, el PEP evita llamadas repetidas a nuestro PDP, lo que lleva a tiempos de respuesta más rápidos y menor latencia para cada solicitud. Disminución de la carga del PDP: El caché minimiza el número de llamadas hechas a nuestro PDP, reduciendo el riesgo de alcanzar límites de tasa o limitación. Mejora de la escalabilidad: Con menos solicitudes llegando a nuestro PDP, nuestra arquitectura puede escalar de manera más eficiente. Reducción de costos: El caché reduce la necesidad de invocaciones repetidas del PDP, lo que reduce directamente los costos de invocación de Lambda.

Resumen y conclusión

Implementar PEP y PDP en nuestro flujo de autorización ofrece una forma muy escalable, flexible y segura de controlar el acceso a los recursos. Al aprovechar AWS Lambda y API Gateway, podemos construir un sistema de autorización sin servidor que separa las preocupaciones de autenticación y autorización, se escala según la demanda y simplifica la gestión de políticas.

Con la adición del Control de Acceso Basado en Roles y DynamoDB para almacenar permisos, combinado con el caché en memoria para un rendimiento mejorado, podemos crear una solución de autorización que se ajuste tanto a las necesidades actuales como futuras.

Entender la diferencia entre los Tokens de ID y los Tokens de Acceso asegura que nuestro sistema use cada uno apropiadamente, ayudándonos a construir un sistema de autorización más seguro y eficiente.

¡Feliz codificación y manténgase seguro!

Código fuente

Toda la configuración, con instrucciones detalladas de despliegue y todo el código, se puede encontrar en Serverless Handbook PEP y PDP

Palabras finales

¡No olvide seguirme en LinkedIn y X para más contenido, y leer el resto de mis Blogs

Como dice Werner! ¡Ahora ve a construir!