PEP und PDP für sichere Autorisierung mit Cognito

Diese Datei wurde automatisch von KI ubersetzt, es konnen Fehler auftreten
In einem meiner früheren Beiträge Building a serverless connected BBQ as SaaS - Teil 4 - AuthZ habe ich das Thema Authentifizierung und Autorisierung mit verteilten PEPs (Policy Enforcement Points) und einem zentralen PDP (Policy Decision Point) angesprochen. In diesem Beitrag werde ich noch tiefer gehen und diesen Aufbau erweitern. Ich werde untersuchen, wie diese Konzepte in der Praxis funktionieren, welche Vorteile sie bieten und wie wir sie in unserer serverlosen Architektur mit AWS Lambda, API Gateway und Cognito User Pools nutzen können.
Außerdem werde ich über das Role-Based Access Control (RBAC)-Modell sprechen, wie man es mit Cognito Groups und DynamoDB implementiert und wie Caching die Leistung unseres Autorisierungsystems steigern kann.
Die gesamte Einrichtung, mit detaillierten Bereitstellungsanweisungen und allem Code, findet man auf Serverless Handbook PEP und PDP
Lassen Sie uns mit einer kurzen Wiederholung beginnen.
Authentifizierung (AuthN) vs. Autorisierung (AuthZ)
Es ist entscheidend, zwischen Authentifizierung und Autorisierung zu unterscheiden, zwei Begriffe, die oft vermischt werden und die ich in vielen Gelegenheiten erklären musste, aber sehr unterschiedliche Zwecke erfüllen.
Authentifizierung (AuthN)
Bei der Authentifizierung geht es darum, die Identität zu überprüfen. Sie beantwortet die Frage: Wer bist du? Wenn sich ein Benutzer in unserer Anwendung anmeldet, stellt die Authentifizierung sicher, dass er der ist, für den er sich ausgibt. Dies könnte etwas so Einfaches wie ein Benutzername und Passwort oder eine komplexere Multi-Faktor-Authentifizierung (MFA) sein.
Autorisierung (AuthZ)
Sobald die Identität eines Benutzers authentifiziert ist, kommt die Autorisierung ins Spiel. Dieser Prozess beantwortet die Frage: Was kannst du tun? Die Autorisierung bestimmt, auf welche Ressourcen, Daten und Aktionen ein Benutzer basierend auf seinen Rollen und Berechtigungen zugreifen darf.
Was sind PEP und PDP?
Bevor wir uns damit beschäftigen, wie man sie in AWS implementiert, ist es wichtig zu verstehen, welche Rollen PEP und PDP bei der Autorisierung spielen.
PEP (Policy Enforcement Point)
Einfach ausgedrückt, ist der PEP der Torwächter. Es sind die Punkte in unserem System, an denen Zugriffsentscheidungen durchgesetzt werden. Wenn ein Benutzer versucht, auf eine geschützte Ressource zuzugreifen, ist der PEP die Komponente, die überprüft, ob die Anfrage basierend auf den Berechtigungen des Benutzers erlaubt oder abgelehnt wird.
In unserem Fall fungiert der Lambda Authorizer in API Gateway als PEP. Der Lambda Authorizer fängt jede eingehende API-Anfrage ab, validiert das JWT-Token (typischerweise von Cognito User Pool oder einem anderen Identitätsanbieter) und leitet die Benutzerinformationen (Claims) zur Autorisierungsbewertung an den PDP weiter.
Der PEP stellt sicher, dass das JWT gültig ist, prüft dessen Ablaufdatum, überprüft die Signatur und validiert Claims (wie aud, iss und sub). Anschließend gibt er die Claims an den PDP weiter, um eine endgültige Entscheidung darüber zu treffen, ob der Benutzer berechtigt ist, auf die angeforderte Ressource zuzugreifen.
PDP (Policy Decision Point)
Der PDP ist der Ort, an dem die Autorisierungslogik resides. Nachdem der PEP das JWT überprüft und sichergestellt hat, dass das Token gültig ist, entscheidet der PDP, ob der Benutzer berechtigt ist, auf die angeforderte Ressource zuzugreifen, basierend auf seinen Rollen, Berechtigungen oder Richtlinien.
Der PDP ist ein separater Mikrodienst. In unserem Fall eine Lambda-Funktion, die die eigentliche Autorisierungsentscheidung trifft. Er überprüft die Rollen des Benutzers, die im groups-Claim im JWT (von Cognito) gespeichert sind, und vergleicht sie mit den Berechtigungen, die für den Zugriff auf eine bestimmte Ressource erforderlich sind und in einem Datenspeicher abgelegt sind. In unserem Fall werden wir DynamoDB verwenden.
Der PDP validiert, ob der Benutzer die notwendigen Berechtigungen hat (wie Admin, User oder Manager), um auf die Ressource zuzugreifen (z. B. GET /admin, POST /profile). Der PDP kann auch zusätzliche Geschäftslogik einbeziehen, wie z. B. die Überprüfung von zeitbasiertem Zugriff oder Geo-Fencing.
Vorteile der Verwendung von PEP und PDP bei der Autorisierung
Die Implementierung von verteilten PEP und zentralen PDP bietet mehrere Vorteile, insbesondere wenn unsere Anwendungen skalieren.
Trennung von Aufgaben Durch die Aufteilung der Aufgaben in Durchsetzung (PEP) und Entscheidung (PDP) erhalten wir saubereren, besser wartbaren Code. Der Lambda Authorizer (PEP) konzentriert sich rein auf Validierung und Durchsetzung. Während der PDP der Richtlinienauswertung gewidmet ist.
Reduzierte Latenz: Indem wir PEPs nahe an den Ort platzieren, an dem Entscheidungen durchgesetzt werden müssen, können wir die Latenz reduzieren, mit einer Caching-Strategie kann diese noch weiter reduziert werden.
Verwaltung: Mit einem zentralen PDP ist unsere gesamte Autorisierungslogik an einem Ort zentriert. Dies erleichtert die Verwaltung und Aktualisierung von Richtlinien, wenn sich unsere Anforderungen weiterentwickeln. Ob es sich um die Änderung von Rollen oder die Hinzufügung neuer Berechtigungssätze handelt, ein zentraler PDP reduziert den Aufwand, Richtlinien an mehreren Stellen zu aktualisieren.
Konsistenz und Compliance: Jede Anfrage wird gegen denselben Satz von Richtlinien ausgewertet, was konsistente Entscheidungen in unserem gesamten System gewährleistet.
Skalierbarkeit: Sowohl die PEP- als auch die PDP-Komponenten skalieren unabhängig nach Bedarf. Wenn unser System ein größeres Anfragenvolumen bewältigen muss, können API Gateway und Lambda automatisch skalieren. Darüber hinaus kann der PDP durch die Implementierung von Caching für die Leistung optimiert werden.
Flexibilität: Ein PDP ermöglicht es uns, das Autorisierungsmodell an unsere Bedürfnisse anzupassen. Wenn sich unsere Anforderungen ändern (zum Beispiel die Umstellung auf attributbasierte Zugriffssteuerung (ABAC) oder die Einführung eines feineren Berechtigungssystems), können wir den PDP leicht anpassen, um diese Änderungen zu berücksichtigen, ohne andere Teile des Systems zu beeinflussen.
Verwenden von PEP und PDP in AWS mit serverloser Architektur
In AWS passt die Integration von PEP und PDP perfekt zu serverlosen Komponenten wie Lambda und API Gateway.
PEP - API Gateway Lambda Authorizer
Wenn ein Client eine Anfrage an unseren API Gateway-Endpunkt sendet, fängt der Lambda Authorizer (PEP) die Anfrage ab, bevor sie unseren Backendservice erreicht. Unsere Implementierung wird mehrere Schlüsselschritte ausführen.
JWT-Validierung: Er decodiert das JWT, validiert die Signatur und prüft, ob das Token abgelaufen ist.
Weiterleitung von Claims: Nach der Überprüfung des Tokens leitet der Lambda Authorizer die Claims (wie sub, groups und role) zur weiteren Autorisierungsprüfung an den PDP weiter. In unserer Lösung werden wir tatsächlich das gesamte JWT-Token weiterleiten.
Um die Anzahl der Aufrufe an unseren PEP und auch PDP zu reduzieren, können wir das Autorisierungscache nutzen, das in API Gateway existiert.
PDP - Autorisierungslogik Lambda-Funktion
Der PDP ist in unserer Fall als separate Lambda-Funktion implementiert und erhält das gesamte JWT-Token oder Claims, um die Autorisierungslogik auszuführen, die mehrere Schritte umfasst.
- Überprüfen der Rolle des Benutzers (mit dem
groups-Claim von Cognito). - Abfragen einer DynamoDB-Tabelle, die Rollen-zu-Berechtigungs-Zuordnungen enthält (z. B. welche Rollen Zugriff auf welche API-Endpunkte haben).
- Auswerten, ob die Rolle des Benutzers mit den erforderlichen Berechtigungen für die angeforderte Ressource oder das API-Endpunkt übereinstimmt.
ID-Token vs Access-Token
Während wir den PEP- und PDP-Workflow implementieren, ist es wichtig, den Unterschied zwischen ID-Tokens und Access-Tokens zu verstehen, da beide oft in AutorisierungsWorkflows verwendet werden.
ID-Token
Das ID-Token wird hauptsächlich für die Authentifizierung verwendet und enthält Informationen darüber, wer der Benutzer ist. Es enthält Claims über die Identität des authentifizierten Benutzers, wie Name, E-Mail und Telefonnummer.
Access-Token
Das Access-Token wird verwendet, um dem Benutzer Zugriff auf geschützte Ressourcen zu gewähren, Autorisierung. Das Access-Token enthält Informationen über die Berechtigungen des Benutzers, wie z. B. welche Ressourcen er zugreifen darf und welche Bereiche ihm gewährt wurden, die definieren, was der Benutzer tun kann (z. B. read:profile, write:profile). Das Access-Token enthält nicht den aud-Claim.
Tokenanpassung in Cognito
Mit dem Pre Token Generation Lambda-Trigger konnten wir früher nur das ID-Token anpassen, daher wurde es oft auch für die Autorisierung verwendet. Mit der Einführung des neuen V2-Events in Cognito User Pools können wir sowohl das ID- als auch das Access-Token anpassen.
Implementierung von PEP und PDP
Nach dieser Einführung lassen Sie uns in die Implementierung eines PEP und PDP mit RBAC eintauchen. Unser PEP wird der Lambda Authorizer in API Gateway sein und unser PDP wird eine separate Lambda-Funktion sein. Der PDP wird Cognito Groups und DynamoDB für die RBAC-Autorisierungslogik verwenden.
Architekturüberblick
Nur als Erinnerung, der gesamte Code und die gesamte Architektur finden Sie auf Serverless Handbook PEP und PDP
In dieser Lösung werden wir unseren PEP mit Lambda Authorizer in API Gateway implementieren. Der PDP wird in diesem Fall ebenfalls mit einer Lambda-Funktion implementiert. Wir werden Benutzern eine Rolle zuweisen, indem wir Cognito Groups verwenden, und wir speichern eine Rollen-Berechtigungs-Zuordnung in DynamoDB.

Um den Ablauf während eines API-Zugriffs besser zu verstehen.

Wie man sieht, werden wir kein API Gateway für unseren PDP verwenden. Stattdessen wird unser PEP die PDP-Lambda-Funktion aufrufen. Es gibt natürlich Vor- und Nachteile bei diesem Ansatz. Auf der Pro-Seite haben wir eine geringere Latenz, ein direkter Lambda-Aufruf ist oft schneller als ein API-Aufruf. Geringere Kosten, da wir nicht für die API-Gateway-Aufrufe bezahlen müssen. Auf der Rückseite schaffen wir eine engere Kopplung und die Änderung der PDP-Implementierung könnte schwieriger werden. Wir müssten einen separaten Cache im PDP implementieren, mit einem API Gateway könnten wir den API Gateway-Cache nutzen.
Die von Ihnen gewählte Vorgehensweise muss jedoch fallweise entschieden werden, es gibt keine allgemeine Regel, wie genau dies implementiert werden soll.
Bereitstellung von Authentifizierung und Cognito
Das Erste, was wir tun werden, ist, Cognito und die benötigten Ressourcen für die Anmeldung bereitzustellen und einzurichten. Wir werden den Cognito User Pool einrichten, die verwaltete Anmeldung konfigurieren und eine einfache Website, die die Rückmeldungen von Cognito verarbeitet und unsere JWT-Tokens anzeigt. Der Einfachheit halber wird es nur eine statische HTML-Seite von CloudFront und einige Lambda@Edge-Funktionen sein. Ich werde das Setup verwenden, das ich in diesem Blog-Beitrag beschrieben habe, also empfehle ich Ihnen für eine detaillierte Betrachtung, diesen zu lesen.
Als ersten Schritt deployen wir also Lambda@Edge, CloudFront-Distribution und SSL-Zertifikat von Serverless Handbook PEP und PDP
Als nächstes lassen Sie uns Cognito bereitstellen und einrichten. Wir werden den UserPool, einen Client, den Anmelde-Stil usw. erstellen.
AWSTemplateFormatVersion: "2010-09-09"
Transform: "AWS::Serverless-2016-10-31"
Description: Erstellt den User Pool und Client für die Authentifizierung
Parameter:
ApplicationName:
Type: String
Description: Die Anwendung, die diesen Aufbau besitzt.
DomainName:
Type: String
Description: Der Domänenname, der für Cloudfront verwendet werden soll
HostedAuthDomainPrefix:
Type: String
Description: Der Domänenpräfix, der für die UserPool-gehostete UI verwendet wird <HostedAuthDomainPrefix>.auth.[region].amazoncognito.com
Ressourcen:
UserPool:
Type: AWS::Cognito::UserPool
Eigenschaften:
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
Eigenschaften:
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
Eigenschaften:
Domain: !Ref HostedAuthDomainPrefix
ManagedLoginVersion: 2
UserPoolId: !Ref UserPool
ManagedLoginStyle:
Type: AWS::Cognito::ManagedLoginBranding
Eigenschaften:
ClientId: !Ref UserPoolClient
UserPoolId: !Ref UserPool
UseCognitoProvidedValues: true
UserPoolIdParameter:
Type: AWS::SSM::Parameter
Eigenschaften:
Name: !Sub /${ApplicationName}/userPoolId
Type: String
Value: !Ref UserPool
Beschreibung: SSM-Parameter für die User Pool-Id
Tags:
ApplicationName: !Ref ApplicationName
UserPoolHostedUiParameter:
Type: AWS::SSM::Parameter
Eigenschaften:
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
Beschreibung: SSM-Parameter für die User Pool-gehostete UI
Tags:
ApplicationName: !Ref ApplicationName
Ausgaben:
CognitoUserPoolJwksUri:
Value: !Sub https://cognito-idp.${AWS::Region}.amazonaws.com/${UserPool}/.well-known/jwks.json
Beschreibung: Die UserPool-jwks-URI
Export:
Name: !Sub ${AWS::StackName}:jwks-url
CognitoUserPoolID:
Value: !Ref UserPool
Beschreibung: Die UserPool-ID
CognitoAppClientID:
Value: !Ref UserPoolClient
Beschreibung: Der App-Client
Export:
Name: !Sub ${AWS::StackName}:app-audience
CognitoUrl:
Beschreibung: Die 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
Beschreibung: Die gehostete UI-URL
Nach dieser Bereitstellung können wir zur Konsole wechseln und Gruppen erstellen, denen Benutzer hinzugefügt werden können. Ich werde drei Gruppen erstellen, Admin, Developer und Test. Klicken Sie auf Gruppe erstellen und geben Sie einen Namen ein. Der Gruppenname würde die Rolle darstellen, die der Benutzer haben wird und bestimmen, welche Berechtigungen er/sie erhalten wird, mehr zu diesem Setup weiter unten.

Wir können dann einige Benutzer erstellen und ihnen eine der Gruppen zuweisen.
Um dieses Setup zu testen, können wir zur mit der CloudFront-Distribution bereitgestellten Webseite navigieren und die JWT-Tokens, Cookies inspizieren.

Wenn wir das Access-Token kopieren und entschlüsseln, verwende ich jwt.io, können wir sehen, dass mein Benutzer den Claim cognito:groups hat, den unser PEP und PDP später für Berechtigungen verwenden werden.
Einrichtung und Bereitstellung von PDP
Als nächstes können wir unseren Autorisierungsdienst, unseren PDP, bereitstellen, der für die Entscheidungsfindung bezüglich Berechtigungen verantwortlich ist.
Die Logik wird in einer Lambda-Funktion implementiert und um rollenbasierte Berechtigungen zu verwalten, erstellen wir eine DynamoDB-Tabelle, die Berechtigungen für jede Rolle speichert. Jede Berechtigung definiert, auf welche Ressourcen der Benutzer zugreifen kann, dies könnte ein bestimmtes API-Ende oder eine HTTP-Methode sein, aber natürlich nicht darauf beschränkt. Wir werden die Tabellendaten modellieren
PK (Partition Key): Die Rolle (z. B. Admin, User). SK (Sort Key): Die Ressource, zum Beispiel Ende und Methode z. B. GET /unicorn. Action: Die Aktion z. B. GET, PUT, WRITE, READ, LIST usw. Ressource: Die Ressource, zum Beispiel das Ende /unicorn Effect: Der Effekt, Erlauben oder Verweigern Beschreibung: Eine Beschreibung der Berechtigung.
| PK | SK | Aktion | Ressource | Effekt | Beschreibung |
|---|---|---|---|---|---|
| Admin | GET /unicorn | GET | /unicorn | Erlauben | Admin kann alle Einhörner zugreifen |
| Test | POST /unicorn | POST | /unicorn | Erlauben | Test kann Einhörner posten |
| Entwickler | DELETE /unicorn | LÖSCHEN | /unicorn | Verweigern | Manager kann ein Einhorn nicht löschen |
Dies ermöglicht es uns, effizient die Berechtigungen für jede Rolle mit einer einfachen DynamoDB-Abfrage nachzuschlagen.
AWSTemplateFormatVersion: "2010-09-09"
Transform: "AWS::Serverless-2016-10-31"
Description: Connected BBQ Application Tenant Service
Parameter:
ApplicationName:
Type: String
Description: Name der übergeordneten Anwendung
UserManagementStackName:
Type: String
Description: Der Name des Stacks, der den Benutzermanagement-Teil enthält, z. B. den Cognito UserPool
Globals:
Funktion:
Timeout: 30
MemorySize: 2048
Architectures:
- arm64
Runtime: python3.12
Ressourcen:
PermissionsTable:
Type: AWS::DynamoDB::Table
Eigenschaften:
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
Eigenschaften:
CodeUri: Lambda/AuthZ
Handler: authz.handler
Policies:
- DynamoDBReadPolicy:
TableName: !Ref PermissionsTable
Umgebung:
Variablen:
JWKS_URL:
Fn::ImportValue: !Sub ${UserManagementStackName}:jwks-url
AUDIENCE:
Fn::ImportValue: !Sub ${UserManagementStackName}:app-audience
PERMISSIONS_TABLE:
!Ref PermissionsTable
Ausgaben:
PDPLambdaArn:
Value: !GetAtt LambdaPDPFunction.Arn
Beschreibung: Die ARN der PDP-Lambda-Funktion
Export:
Name: !Sub ${AWS::StackName}:pdp-lambda-arn
PDPLambdaName:
Value: !Ref LambdaPDPFunction
Beschreibung: Der Name der PDP-Lambda-Funktion
Export:
Name: !Sub ${AWS::StackName}:pdp-lambda-nameRollenautorisierungslogik
Die PDP-Lambda decodiert das JWT, ruft die Rolle aus dem cognito:groups-Claim ab und fragt die DynamoDB-Tabelle ab, um zu überprüfen, ob die Rolle die Berechtigung hat, auf die angeforderte Ressource zuzugreifen.
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("Unautorisiert: Rolle nicht im Token gefunden")
if validate_permission(role, action, resource):
response_body = generate_access(
decoded_token["sub"], "Erlauben", action, resource
)
return {
"statusCode": 200,
"body": json.dumps(response_body),
"headers": {"Content-Type": "application/json"},
}
except Exception as e:
print(f"Autorisierungsfehler: {str(e)}")
response_body = generate_access(decoded_token["sub"], "Verweigern", 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 Rolle: {role}, Aktion: {action}, Ressource: {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"] == "Erlauben":
return True
else:
return False
except ClientError as e:
print(f"Fehler bei der DynamoDB-Abfrage: {e}")
return False
def generate_access(principal, effect, action, resource):
auth_response = {
"principalId": principal,
"effect": effect,
"action": action,
"resource": resource,
}
return auth_response
Bereitstellung von API und PEP
Jetzt können wir unsere API und PEP, Lambda Authorizer, bereitstellen.
AWSTemplateFormatVersion: "2010-09-09"
Transform: "AWS::Serverless-2016-10-31"
Description: Erstellen Sie die API für die Selbstbedienung von Zertifikaten
Parameter:
ApplicationName:
Type: String
Description: Name der übergeordneten Anwendung
UserManagementStackName:
Type: String
Description: Der Name des Stacks, der den Benutzermanagement-Teil enthält, z. B. den Cognito UserPool
PDPStackName:
Type: String
Description: Der Name des Stacks, der den PDP-Dienst enthält
Globals:
Funktion:
Timeout: 30
MemorySize: 2048
Runtime: python3.12
Ressourcen:
LambdaGetUnicorn:
Type: AWS::Serverless::Function
Eigenschaften:
CodeUri: Lambda/API/GetUnicorn
Handler: handler.handler
Ereignisse:
GetUnicorns:
Type: Api
Eigenschaften:
Path: /unicorn
Methode: get
RestApiId: !Ref UnicornApi
UnicornApi:
Type: AWS::Serverless::Api
Eigenschaften:
Beschreibung: API zum Erstellen und Verwalten von Einhörnern
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
StandardAuthorizer: LambdaRequestAuthorizer
LambdaApiAuthorizer:
Type: AWS::Serverless::Function
Eigenschaften:
CodeUri: Lambda/Authorizer/
Handler: auth.handler
Policies:
- LambdaInvokePolicy:
FunctionName:
Fn::ImportValue: !Sub ${PDPStackName}:pdp-lambda-name
Umgebung:
Variablen:
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
Wir setzen unseren PEP als Standard-Autorisierer, damit er zu jeder Ressource und Methode hinzugefügt wird. Um die Anzahl der Aufrufe an unseren PDP zu reduzieren, wird das Autorisierungscache in API Gateway mit einer TTL von 600 Sekunden verwendet.
PEP-Autorisierungslogik
Der PEP Lambda Authorizer decodiert das JWT, überprüft die Gültigkeit und ruft dann den PDP für eine endgültige Berechtigungsentscheidung auf.
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("Unautorisiert")
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"Autorisierungsfehler: {str(e)}")
return generate_policy(
decoded_token["sub"], "Verweigern", 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
Bedeutung von Caching
Caching ist wichtig für die Optimierung unseres Autorisierungsflusses. Durch die Reduzierung der Aufrufe an den PDP und die Beschleunigung der Entscheidungsfindung hilft Caching, die Gesamtleistung, Skalierbarkeit und Kosteneffizienz unserer Anwendung zu verbessern.
Reduzierte Latenz: Durch das Caching von Rollen- und Berechtigungsdaten vermeidet der PEP wiederholte Aufrufe an unseren PDP, was zu schnelleren Antwortzeiten und geringerer Latenz für jede Anfrage führt. Geringere PDP-Last: Caching minimiert die Anzahl der Aufrufe an unseren PDP, wodurch das Risiko der Erreichung von Ratenbegrenzungen oder Throttling verringert wird. Verbesserte Skalierbarkeit: Mit weniger Anfragen an unseren PDP kann unsere Architektur effizienter skalieren. Geringere Kosten: Caching reduziert den Bedarf an wiederholten PDP-Aufrufen, was direkt die Lambda-Aufrufkosten senkt.
Zusammenfassung und Fazit
Die Implementierung von PEP und PDP in unserem Autorisierungsfluss bietet eine hochgradig skalierbare, flexible und sichere Möglichkeit, den Zugriff auf Ressourcen zu kontrollieren. Durch die Nutzung von AWS Lambda und API Gateway können wir ein serverloses Autorisierungssystem erstellen, das Authentifizierungs- und Autorisierungsprobleme trennt, bei Bedarf skaliert und die Richtlinienerwaltung vereinfacht.
Mit der Hinzufügung von Role-Based Access Control und DynamoDB zur Speicherung von Berechtigungen, kombiniert mit In-Memory-Caching für verbesserte Leistung, können wir eine Autorisierungslösung erstellen, die sowohl den aktuellen als auch den zukünftigen Anforderungen entspricht.
Das Verständnis des Unterschieds zwischen ID-Tokens und Access-Tokens stellt sicher, dass unser System jedes angemessen verwendet, wodurch wir ein sichereres und effizienteres Autorisierungssystem aufbauen können.
Viel Spaß beim Codieren und bleiben Sie sicher!
Quellcode
Die gesamte Einrichtung, mit detaillierten Bereitstellungsanweisungen und allem Code, finden Sie auf Serverless Handbook PEP und PDP
Abschließende Worte
Vergessen Sie nicht, mir auf LinkedIn und X zu folgen, um weitere Inhalte zu erhalten, und lesen Sie den Rest meiner Blogs
Wie Werner sagt! Jetzt loslegen!