aws, authz, serverless

PEP et PDP pour une autorisation sécurisée avec Cognito

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

Ce fichier a ete traduit automatiquement par IA, des erreurs peuvent survenir

Dans l'un de mes articles précédents Construire un BBQ connecté sans serveur en tant que SaaS - Partie 4 - AuthZ j'ai abordé le sujet de l'Authentication et de l'Authorization avec des PEP (Points de Mise en Application de Politique) distribués et un PDP (Point de Décision de Politique) centralisé. Dans cet article, je vais creuser un peu plus profondément et élargir cette configuration. J'explorerai comment ces concepts fonctionnent en pratique, les avantages qu'ils offrent, et comment nous pouvons les exploiter dans notre architecture sans serveur en utilisant AWS Lambda, API Gateway et Cognito User Pools.

De plus, je parlerai du modèle de contrôle d'accès basé sur les rôles (RBAC), comment l'implémenter à l'aide de groupes Cognito et de DynamoDB, et comment le cache peut booster les performances de notre système d'autorisation.

Toute la configuration, avec des instructions détaillées de déploiement, et tout le code peuvent être trouvés sur Serverless Handbook PEP et PDP

Commençons par un bref rappel.

Authentication (AuthN) vs. Authorization (AuthZ)

Il est crucial de faire la distinction entre Authentication et Authorization, deux termes qui sont souvent confondus, et que j'ai dû expliquer à de nombreuses reprises, mais qui servent des objectifs très différents.

Authentication (AuthN)

L'Authentication consiste à vérifier l'identité. Elle répond à la question, Qui êtes-vous ? Lorsqu'un utilisateur se connecte à notre application, l'authentication garantit qu'il est bien celui qu'il prétend être. Cela peut impliquer quelque chose d'aussi simple qu'un nom d'utilisateur et un mot de passe ou une authentication multifacteur (MFA) plus complexe.

Authorization (AuthZ)

Une fois l'identité de l'utilisateur authentifiée, l'authorization entre en jeu. Ce processus répond à la question, Que pouvez-vous faire ? L'authorization détermine quels ressources, données et actions un utilisateur est autorisé à accéder en fonction de ses rôles et permissions.

Que sont les PEP et PDP ?

Avant de plonger dans leur implémentation sur AWS, il est essentiel de comprendre les rôles que jouent les PEP et PDP dans l'autorisation.

PEP (Point de Mise en Application de Politique)

En termes simples, le PEP est le gardien. Ce sont les points de notre système où les décisions de contrôle d'accès sont appliquées. Lorsqu'un utilisateur tente d'accéder à une ressource protégée, le PEP est le composant chargé de vérifier si la requête est autorisée ou refusée en fonction des permissions de l'utilisateur.

Dans notre cas, l'Authorizer Lambda dans API Gateway agit comme le PEP. L'Authorizer Lambda intercepte chaque requête API entrante, valide le jeton JWT (généralement à partir de Cognito User Pool ou de n'importe quel fournisseur d'identité), et transmet les informations de l'utilisateur (claims) au PDP pour l'évaluation de l'autorisation.

Le PEP garantit que le JWT est valide, vérifie son expiration, vérifie sa signature, et valide les claims (comme aud, iss, et sub). Il transmet ensuite les claims au PDP pour une décision finale sur Whether l'utilisateur est autorisé à accéder à la ressource demandée.

PDP (Point de Décision de Politique)

Le PDP est l'endroit où réside la logique d'autorisation. Une fois que le PEP a vérifié le JWT et garantit que le jeton est valide, le PDP détermine si l'utilisateur est autorisé à accéder à la ressource demandée en fonction de ses rôles, permissions ou politiques.

Le PDP est un service micro indépendant, dans notre cas une fonction Lambda, qui effectue la décision d'autorisation réelle. Il vérifie les rôles de l'utilisateur, stockés dans la claim groups du JWT (de Cognito), et les compare aux permissions requises pour accéder à une ressource spécifique, stockées dans un stockage de données. Dans notre cas nous utiliserons DynamoDB.

Le PDP valide si l'utilisateur a les permissions nécessaires (comme Admin, User, ou Manager) pour accéder à la ressource (par exemple, GET /admin, POST /profile). Le PDP peut également intégrer une logique métier supplémentaire, comme la vérification de l'accès basé sur le temps ou le géofencing.

Avantages de l'utilisation de PEP et PDP dans l'Authorization

L'implémentation de PEP distribués et d'un PDP centralisé offre plusieurs avantages, en particulier à mesure que nos applications évoluent.

Séparation des responsabilités En séparant les responsabilités de mise en application (PEP) et de décision (PDP), nous obtenons un code plus propre et plus facile à maintenir. L'Authorizer Lambda (PEP) est uniquement axé sur la validation et l'application. Tandis que le PDP est dédié à l'évaluation des politiques.

Latence réduite : En plaçant les PEP près de l'endroit où les décisions doivent être appliquées, nous pouvons réduire la latence, avec une stratégie de cache cela peut être encore réduit.

Gestion : Avec un PDP centralisé, toute notre logique d'autorisation est centralisée en un seul endroit. Cela facilite la gestion et la mise à jour des politiques à mesure que nos exigences évoluent. Que ce soit pour modifier des rôles ou ajouter de nouveaux ensembles de permissions, avoir un PDP central réduit la charge de mise à jour des politiques à plusieurs endroits.

Cohérence et conformité : Chaque requête est évaluée par rapport au même ensemble de politiques, garantissant une prise de décision cohérente dans tout notre système.

Scalabilité : Les composants PEP et PDP scalent indépendamment en fonction de la demande. Si notre système doit gérer un plus grand volume de requêtes, API Gateway et Lambda peuvent s'adapter automatiquement. De plus, le PDP peut être optimisé pour les performances en implémentant un cache.

Flexibilité : Un PDP nous permet d'adapter le modèle d'autorisation à nos besoins. Si nos exigences changent (par exemple, passer à un contrôle d'accès basé sur les attributs (ABAC) ou introduire un système de permissions plus granulaire), nous pouvons facilement modifier le PDP pour accueillir ces changements sans affecter d'autres parties du système.

Utilisation de PEP et PDP dans AWS avec une architecture sans serveur

Sur AWS, l'intégration de PEP et PDP s'intègre parfaitement avec les composants sans serveur comme Lambda et API Gateway.

PEP - Lambda Authorizer d'API Gateway

Lorsqu'un client envoie une requête à notre point final API Gateway, le Lambda Authorizer (PEP) intercepte la requête avant qu'elle n'atteigne notre service backend. Notre implémentation effectuera plusieurs étapes clés.

Validation du JWT : Il décode le JWT, valide la signature, et vérifie si le jeton a expiré.

Transmission des claims : Après avoir vérifié le jeton, le Lambda Authorizer transmet les claims (telles que sub, groups, et role) au PDP pour des vérifications d'autorisation supplémentaires. Dans notre solution nous transmettrons en fait le jeton JWT entier.

Pour réduire le nombre d'appels à notre PEP et également au PDP nous pouvons utiliser le cache d'autorisation qui existe dans API Gateway.

PDP - Fonction Lambda de logique d'autorisation

Le PDP est implémenté comme une fonction Lambda séparée, dans notre cas, et recevra le jeton JWT entier, ou les claims, pour effectuer la logique d'autorisation, qui inclura plusieurs étapes.

  • Vérifier le rôle de l'utilisateur (en utilisant la claim groups de Cognito).
  • Interroger une table DynamoDB qui contient des mappages rôle-permission (par exemple, quels rôles ont accès à quelles extrémités API).
  • Évalue si le rôle de l'utilisateur correspond aux permissions requises pour la ressource ou l'extrémité API demandée.

ID Token vs Access Token

Alors que nous implémentons le flux de travail PEP et PDP, il est essentiel de comprendre la différence entre les ID Tokens et les Access Tokens, car les deux sont souvent utilisés dans les flux d'autorisation.

ID Token

Le ID Token est principalement utilisé pour l'authentication et contient des informations sur l'identité de l'utilisateur. Il contient des claims sur l'identité de l'utilisateur authentifié, comme le nom, l'email, et le téléphone.

Access Token

Le Access Token est utilisé pour accorder à l'utilisateur l'accès aux ressources protégées, l'autorisation. Le Access Token contient des informations sur les permissions de l'utilisateur, comme quelles ressources il est autorisé à accéder et les scopes qui lui ont été accordés, qui définissent ce que l'utilisateur peut faire (par exemple, read:profile, write:profile). Le jeton d'accès n'inclut pas la claim aud.

Personnalisation des jetons dans Cognito

Avec le déclencheur Lambda de génération de jetons Pre, nous ne pouvions auparavant personnaliser que le ID Token, donc il était souvent utilisé pour l'autorisation également. Avec l'introduction du nouvel événement V2 dans Cognito User Pools nous pouvons personnaliser à la fois le ID et le Access token.

Implémentation de PEP et PDP

Avec cette introduction terminée, plongeons dans l'implémentation d'un PEP et PDP avec RBAC. Notre PEP sera l'Authorizer Lambda dans API Gateway et notre PDP sera une fonction Lambda séparée. Le PDP utilisera les groupes Cognito et DynamoDB pour la logique d'autorisation RBAC.

Aperçu de l'architecture

Juste comme rappel, tout le code et l'architecture peuvent être trouvés sur Serverless Handbook PEP et PDP

Dans cette solution nous allons implémenter notre PEP en utilisant Lambda Authorizer dans API Gateway. Le PDP dans ce cas sera également implémenté à l'aide d'une fonction Lambda. Nous assignerons aux utilisateurs un Rôle en utilisant Cognito Groups et nous garderons une correspondance Rôle - Permission dans DynamoDB.

Image montrant l'aperçu de l'architecture

Pour mieux comprendre le flux lors d'un accès API.

Image montrant le flux d'appel

Comme on le voit nous n'utiliserons pas un API Gateway pour notre PDP. À la place notre PEP appellera la fonction Lambda PDP. Il y a bien sûr des avantages et des inconvénients à cette approche. Côté avantages nous avons une latence plus faible, un appel Lambda direct est souvent plus rapide qu'un appel API. Une moindre coût car nous n'avons pas à payer pour l'appel API Gateway. Côté inconvénients, nous créons un couplage plus étroit et changer l'implémentation du PDP pourrait être plus difficile. Nous aurions besoin d'implémenter un cache séparé dans le PDP, en utilisant un API Gateway nous pourrions nous reposer sur le cache d'API Gateway.

Cependant, l'approche que vous choisissez doit être une approche cas par cas, il n'y a pas de règle d'or pour exactement comment implémenter cela.

Déploiement de l'authentication et de Cognito

La première chose que nous ferons est de déployer et de configurer Cognito et les ressources nécessaires pour la connexion. Nous configurerons le UserPool Cognito, la connexion gérée, et un simple site web qui gérera les callbacks de Cognito et affichera nos jetons JWT. Pour simplifier ce sera juste une page html statique depuis CloudFront et quelques fonctions Lambda@Edge. J'utiliserai la configuration que j'ai décrite dans ce blog post, donc pour une plongée profonde je vous recommande de le lire.

Donc comme première étape déployez Lambda@Edge, la distribution CloudFront, et le certificat SSL depuis Serverless Handbook PEP et PDP

Ensuite, déployons et configurons Cognito. Nous créerons le UserPool, un client, le style de connexion, etc.

AWSTemplateFormatVersion: "2010-09-09"
Transform: "AWS::Serverless-2016-10-31"
Description: Crée le User Pool et le Client utilisés pour l'Authentication
Parameters:
  ApplicationName:
    Type: String
    Description: L'application qui possède cette configuration.
  DomainName:
    Type: String
    Description: Le nom de domaine à utiliser pour cloudfront
  HostedAuthDomainPrefix:
    Type: String
    Description: Le préfixe de domaine à utiliser pour l'interface utilisateur hébergée UserPool <HostedAuthDomainPrefix>.auth.[region].amazoncognito.com

Resources:
  UserPool:
    Type: AWS::Cognito::UserPool
    Properties:
      UsernameConfiguration:
        CaseSensitive: false
      AutoVerifiedAttributes:
        - email
      UserPoolName: !Sub ${ApplicationName}-user-pool
      Schema:
        - Name: email
          AttributeDataType: String
          Mutable: false
          Required: true
        - Name: name
          AttributeDataType: String
          Mutable: true
          Required: true

  UserPoolClient:
    Type: AWS::Cognito::UserPoolClient
    Properties:
      UserPoolId: !Ref UserPool
      GenerateSecret: True
      AllowedOAuthFlowsUserPoolClient: true
      CallbackURLs:
        - !Sub https://${DomainName}/signin
      AllowedOAuthFlows:
        - code
        - implicit
      AllowedOAuthScopes:
        - phone
        - email
        - openid
        - profile
      SupportedIdentityProviders:
        - COGNITO

  HostedUserPoolDomain:
    Type: AWS::Cognito::UserPoolDomain
    Properties:
      Domain: !Ref HostedAuthDomainPrefix
      ManagedLoginVersion: 2
      UserPoolId: !Ref UserPool

  ManagedLoginStyle:
    Type: AWS::Cognito::ManagedLoginBranding
    Properties:
      ClientId: !Ref UserPoolClient
      UserPoolId: !Ref UserPool
      UseCognitoProvidedValues: true

  UserPoolIdParameter:
    Type: AWS::SSM::Parameter
    Properties:
      Name: !Sub /${ApplicationName}/userPoolId
      Type: String
      Value: !Ref UserPool
      Description: Paramètre SSM pour l'ID du User Pool
      Tags:
        ApplicationName: !Ref ApplicationName

  UserPoolHostedUiParameter:
    Type: AWS::SSM::Parameter
    Properties:
      Name: !Sub /${ApplicationName}/userPoolHostedUi
      Type: String
      Value: !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
      Description: Paramètre SSM pour l'Interface Utilisateur Hébergée du User Pool
      Tags:
        ApplicationName: !Ref ApplicationName

Outputs:
  CognitoUserPoolJwksUri:
    Value: !Sub https://cognito-idp.${AWS::Region}.amazonaws.com/${UserPool}/.well-known/jwks.json
    Description: L'URI jwks du UserPool
    Export:
      Name: !Sub ${AWS::StackName}:jwks-url
  CognitoUserPoolID:
    Value: !Ref UserPool
    Description: L'ID du UserPool
  CognitoAppClientID:
    Value: !Ref UserPoolClient
    Description: Le client de l'application
    Export:
      Name: !Sub ${AWS::StackName}:app-audience
  CognitoUrl:
    Description: L'URL
    Value: !GetAtt UserPool.ProviderURL
  CognitoHostedUI:
    Value: !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
    Description: L'URL de l'interface utilisateur hébergée

Avec ce déploiement effectué nous pouvons passer à la Console et créer des groupes auxquels les utilisateurs peuvent être ajoutés. Je vais créer trois groupes, Admin, Developer, et Test. Cliquez sur Créer un groupe et donnez-lui un nom. Le nom du groupe représentera le Rôle que l'utilisateur aura et déterminera quelles permissions il/elle obtiendra, plus d'informations sur cette configuration plus loin.

Image montrant les groupes cognito

Nous pouvons ensuite créer quelques utilisateurs et les assigner à l'un des groupes.

Pour tester cette configuration nous pouvons naviguer vers la page web déployée avec la distribution CloudFront et inspecter les jetons JWT, les cookies.

Image montrant les cookies

Si nous copions le jeton d'accès et le décodons, j'utilise jwt.io, nous pouvons voir que mon utilisateur a la claim cognito:groups que notre PEP et PDP utiliseront plus tard pour les permissions.

Configuration et déploiement du PDP

Ensuite nous pouvons déployer notre service d'Authorization, notre PDP, responsable de prendre les décisions de permission.

La logique sera implémentée dans une fonction Lambda et pour gérer les permissions basées sur les rôles, nous créerons une table DynamoDB qui stockera les permissions pour chaque rôle. Chaque permission définit quelles ressources l'utilisateur peut accéder, cela pourrait être une extrémité API spécifique et la méthode HTTP, mais bien sûr pas limité à cela. Nous modéliserons les données de la table

PK (Clé de partition): Le Rôle (par exemple, Admin, User). SK (Clé de tri): La ressource, par exemple l'extrémité et la Méthode par exemple GET /unicorn. Action: L'action par exemple GET, PUT, WRITE, READ, LIST etc Resource: La Ressource, par exemple l'extrémité /unicorn Effect: L'Effet, Allow ou Deny Description: Une description de la permission.

PKSKActionResourceEffectDescription
AdminGET /unicornGET/unicornAllowAdmin peut accéder de tous les unicorns
TestPOST /unicornPOST/unicornAllowTest peut poster sur les unicorns
DeveloperDELETE /unicornDELETE/unicornDenyManager ne peut pas supprimer un unicorn

Cela nous permet de rechercher efficacement les permissions pour chaque rôle à l'aide d'une simple requête DynamoDB.

AWSTemplateFormatVersion: "2010-09-09"
Transform: "AWS::Serverless-2016-10-31"
Description: Application de service de locataire Connected BBQ
Parameters:
  ApplicationName:
    Type: String
    Description: Nom de l'application propriétaire
  UserManagementStackName:
    Type: String
    Description: Le nom de la pile qui contient la partie de gestion des utilisateurs, par exemple le UserPool Cognito

Globals:
  Function:
    Timeout: 30
    MemorySize: 2048
    Architectures:
      - arm64
    Runtime: python3.12

Resources:
  PermissionsTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName:
        Fn::Sub: ${ApplicationName}-pdp-role-permission-map
      BillingMode: PAY_PER_REQUEST
      AttributeDefinitions:
      - AttributeName: PK
        AttributeType: S
      - AttributeName: SK
        AttributeType: S
      KeySchema:
      - AttributeName: PK
        KeyType: HASH
      - AttributeName: SK
        KeyType: RANGE

  LambdaPDPFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: Lambda/AuthZ
      Handler: authz.handler
      Policies:
        - DynamoDBReadPolicy:
            TableName: !Ref PermissionsTable
      Environment:
        Variables:
          JWKS_URL:
            Fn::ImportValue: !Sub ${UserManagementStackName}:jwks-url
          AUDIENCE:
            Fn::ImportValue: !Sub ${UserManagementStackName}:app-audience
          PERMISSIONS_TABLE:
            !Ref PermissionsTable

Outputs:
  PDPLambdaArn:
    Value: !GetAtt LambdaPDPFunction.Arn
    Description: L'ARN de la fonction Lambda PDP
    Export:
      Name: !Sub ${AWS::StackName}:pdp-lambda-arn
  PDPLambdaName:
    Value: !Ref LambdaPDPFunction
    Description: Le nom de la fonction Lambda PDP
    Export:
      Name: !Sub ${AWS::StackName}:pdp-lambda-name

Logique d'autorisation des rôles

Le Lambda PDP décodera le JWT, récupérera le rôle à partir de la claim cognito:groups, et interrogera la table DynamoDB pour vérifier si le rôle a la permission d'accéder à la ressource demandée.

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("Unauthorized: Role not found in the token")

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

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

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

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

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


def validate_permission(role, action, resource):
    print(f"validate_permission Role: {role}, Action: {action}, Resource: {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]["Effect"] == "Allow":
            return True
        else:
            return False
    except ClientError as e:
        print(f"Error querying DynamoDB: {e}")
        return False


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

Déploiement de l'API et du PEP

Maintenant nous pouvons déployer notre API et notre PEP, Lambda Authorizer.

AWSTemplateFormatVersion: "2010-09-09"
Transform: "AWS::Serverless-2016-10-31"
Description: Crée l'API pour la gestion des certificats en auto-service
Parameters:
  ApplicationName:
    Type: String
    Description: Nom de l'application propriétaire
  UserManagementStackName:
    Type: String
    Description: Le nom de la pile qui contient la partie de gestion des utilisateurs, par exemple le UserPool Cognito
  PDPStackName:
    Type: String
    Description: Le nom de la pile qui contient le service PDP

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

Resources:
  LambdaGetUnicorn:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: Lambda/API/GetUnicorn
      Handler: handler.handler
      Events:
        GetUnicorns:
          Type: Api
          Properties:
            Path: /unicorn
            Method: get
            RestApiId: !Ref UnicornApi

  UnicornApi:
    Type: AWS::Serverless::Api
    Properties:
      Description: API pour créer et gérer les Unicorns
      Name: !Sub ${ApplicationName}-api
      StageName: prod
      OpenApiVersion: '3.0.1'
      AlwaysDeploy: true
      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:
        AddDefaultAuthorizerToCorsPreflight: false
        Authorizers:
          LambdaRequestAuthorizer:
            FunctionArn: !GetAtt LambdaApiAuthorizer.Arn
            FunctionPayloadType: REQUEST
            Identity: 
              Headers: 
                - Authorization
              ReauthorizeEvery: 600
        DefaultAuthorizer: LambdaRequestAuthorizer

  LambdaApiAuthorizer:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: Lambda/Authorizer/
      Handler: auth.handler
      Policies:
        - LambdaInvokePolicy:
            FunctionName: 
              Fn::ImportValue: !Sub ${PDPStackName}:pdp-lambda-name
      Environment:
        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

Nous définissons notre PEP comme l'authorizer par défaut de cette façon il sera ajouté à chaque ressource et méthode. Pour réduire le nombre d'appels à notre PDP le cache d'autorisation dans API Gateway est utilisé avec un TTL de 600 secondes.

Logique d'autorisation PEP

L'Authorizer Lambda PEP décodera le JWT, vérificera sa validité, puis appellera le PDP pour une décision finale de permission.

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

lambda_client = boto3.client("lambda")


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

    if not token:
        raise Exception("Unauthorized")

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

    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,
            "resource": path,
            "action": 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"Authorization error: {str(e)}")

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


def generate_policy(principal_id, effect, resource):
    auth_response = {
        "principalId": principal_id,
        "policyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {"Action": "execute-api:Invoke", "Effect": effect, "Resource": resource}
            ],
        },
    }
    return auth_response

Importance du cache

Le cache est important pour optimiser notre flux d'autorisation. En réduisant les appels au PDP et en accélérant la prise de décision, le cache améliore les performances globales, la scalabilité et l'efficacité énergétique de notre application.

Réduire la latence : En mettant en cache les données de rôle et de permission, le PEP évite des appels répétés à notre PDP, ce qui entraîne des temps de réponse plus rapides et une latence plus faible pour chaque requête. Diminuer la charge du PDP : Le cache minimise le nombre d'appels effectués à notre PDP, réduisant le risque d'atteindre des limites de débit ou de throttling. Améliorer la scalabilité : Avec moins de requêtes frappant notre PDP, notre architecture peut s'adapter plus efficacement. Réduire les coûts : Le cache réduit le besoin d'invocations PDP répétées, ce qui réduit directement les coûts d'invocation Lambda.

Résumé et conclusion

L'implémentation de PEP et PDP dans notre flux d'autorisation offre une manière hautement scalable, flexible et sécurisée de contrôler l'accès aux ressources. En utilisant AWS Lambda et API Gateway, nous pouvons construire un système d'autorisation sans serveur qui sépare les préoccupations d'authentication et d'autorisation, s'adapte à la demande et simplifie la gestion des politiques.

Avec l'ajout du contrôle d'accès basé sur les rôles et DynamoDB pour stocker les permissions, combiné à un cache en mémoire pour des performances améliorées, nous pouvons créer une solution d'autorisation qui répond aux besoins actuels et futurs.

Comprendre la différence entre les ID Tokens et les Access Tokens garantit que notre système utilise chacun de manière appropriée, nous aidant à construire un système d'autorisation plus sécurisé et efficace.

Bonne programmation, et restez sécurisés !

Code source

Toute la configuration, avec des instructions détaillées de déploiement, et tout le code peuvent être trouvés sur Serverless Handbook PEP et PDP

Derniers mots

N'oubliez pas de me suivre sur LinkedIn et X pour plus de contenu, et lisez le reste de mes Blogs

Comme le dit Werner ! Maintenant allez construire !