Blog

Absicherung von S3-Downloads mit ALB und Cognito-Authentifizierung

Joris Conijn

Joris Conijn

Aktualisiert Oktober 14, 2025
5 Minuten

Früher war es schwer, einen Endpunkt zu sichern. Heutzutage, mit der Cloud, ist es ganz einfach. Sie müssen nur wissen, wie! Angenommen, Sie haben Dateien auf S3, die Sie gerne weitergeben möchten. Sie könnten das Objekt öffentlich zugänglich machen. So können Ihre Nutzer die Datei ganz einfach über ihren Browser herunterladen. Wenn Sie sie skalieren müssen, können Sie CloudFront hinzufügen. Dies würde die Inhalte näher an Ihren Nutzern zwischenspeichern und dafür sorgen, dass Ihre Nutzer die beste Leistung erhalten. Was aber, wenn Sie kontrollieren möchten, wer die Datei herunterladen kann? Hierfür benötigen Sie eine Authentifizierung und Autorisierung.

Authentifizierung vs. Autorisierung

Bei der Authentifizierung geht es darum, festzustellen, wer Sie sind. Zunächst müssen wir sicherstellen, dass wir wissen, wer der Benutzer ist. Sobald wir wissen, wer der Benutzer ist, können wir prüfen, ob der Benutzer auf den Inhalt zugreifen darf. Letzteres ist die Autorisierung. AWS bietet einen Service namens Cognito, mit dem Sie einen Pool von Benutzern verwalten können. Diese Benutzer können aus anderen Quellen wie Google, Facebook und Ihrem eigenen Identitätsanbieter stammen. Wenn Sie keine Identitätsanbieter verwenden möchten, können Sie Benutzer auch direkt im Benutzerpool anlegen.

Sie können auch Gruppen erstellen und auf der Grundlage dieser Gruppen die Berechtigungen verwalten. Sie könnten zum Beispiel eine Gruppe namens Entwickler erstellen. Alle Benutzer in dieser Gruppe sollten berechtigt sein, den auf S3 gehosteten Build-Bericht abzurufen.

Erstellen des Endpunkts

Mit dem Cognito User Pool benötigen wir eine Möglichkeit, den Benutzer während der Anfrage zu validieren. Ich verwende einen Application Load Balancer, um eine Lambda-Funktion aufzurufen. Diese Funktion kann dann prüfen, ob der Benutzer auf den Bericht zugreifen kann. Die Logik ist ganz einfach: Wenn der Benutzer zur Gruppe der Entwickler gehört, kann er den Bericht lesen.

In diesem Fall können wir die native Cognito-Integration des Application Load Balancer verwenden. Dies hat folgende Auswirkungen:

Wenn der Benutzer nicht authentifiziert ist, navigieren Sie zu der von Cognito gehosteten Benutzeroberfläche. Wenn Sie Ihren Identitätsanbieter verwenden, werden Sie auf die Anmeldeseite Ihres Unternehmens weitergeleitet. Wenn Sie die Benutzer aus dem Benutzerpool hosten, wird dem Benutzer ein Anmeldeformular angezeigt. Nachdem sich der Benutzer über eine Weiterleitung angemeldet hat, ist der Benutzer nun authentifiziert. Der Load Balancer wird nun die Zielgruppe mit der Anfrage aufrufen.

Wir wollen auch prüfen, ob der Benutzer in der Entwicklergruppe ist. Wir werden eine Lambda-Funktion verwenden, um dies zu überprüfen. Der folgende Code würde den Zweck erfüllen:

import base64
import json
from typing import List


def decode(data: str) -> dict:
    return json.loads(base64.b64decode(data.split('.')[1]).decode('utf-8'))


def resolve_groups(groups: str) -> List[str]:
    return list(map(lambda group: group.strip(), groups[1:-1].split(',')))


def handler(event, context) -> dict:
    code = 403
    description = "403 Access Denied"
    body = "Access Denied"
    user = decode(event["headers"]["x-amzn-oidc-data"])
    groups = resolve_groups(user.get('custom:groups', '[]'))

    if 'developers' in groups:
        code = 200
        description = "200 OK"
        body = f"Hi {user.get('name')}, you should be able to download the report"

    return {
        "statusCode": code,
        "statusDescription": description,
        "isBase64Encoded": False,
        "headers": {"Content-Type": "json; charset=utf-8"},
        "body": body,
    }

Der Listener auf dem Application Load Balancer und der User Pool Client können wie folgt konfiguriert werden:

  Listener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      LoadBalancerArn: !Ref LoadBalancer
      Port: 443
      Protocol: HTTPS
      Certificates:
        - CertificateArn: arn:aws:acm:eu-west-1:111122223333:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
      DefaultActions:
        - AuthenticateCognitoConfig:
            OnUnauthenticatedRequest: authenticate
            Scope: openid
            UserPoolArn: arn:aws:cognito-idp:eu-west-1:111122223333:userpool/eu-west-1_xXXXxxxx
            UserPoolClientId: !Ref UserPoolClient
          Order: 1
          Type: authenticate-cognito
        - Order: 2
          TargetGroupArn:
            Ref: LambdaTarget
          Type: forward


  UserPoolClient:
    Type: AWS::Cognito::UserPoolClient
    Properties:
      AllowedOAuthFlows:
        - code
      AllowedOAuthFlowsUserPoolClient: true
      AllowedOAuthScopes:
        - profile
        - phone
        - email
        - openid
        - aws.cognito.signin.user.admin
      CallbackURLs:
        - https://<MyDomainName>/oauth2/idpresponse
      ClientName: MyClient
      GenerateSecret: true
      LogoutURLs:
        - https://<MyDomainName>/logout
      SupportedIdentityProviders:
        - COGNITO
      UserPoolId: eu-west-1_xXXXxxxx

Sie müssen auch die Lambda-Funktion wie folgt einrichten:

  LambdaSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      SecurityGroupEgress:
        - CidrIp: 0.0.0.0/0
          Description: Allow all outbound traffic by default
          IpProtocol: "-1"
      SecurityGroupIngress:
        - CidrIp: 0.0.0.0/0
          Description: Allow HTTPS access
          FromPort: 443
          IpProtocol: tcp
          ToPort: 443
      VpcId: !Ref VPCAsParameter

  LambdaRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
      ManagedPolicyArns:
        - !Sub arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
        - !Sub arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole

  LambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        S3Bucket: my-bucket
        S3Key: path/to/code.zip
      Handler: index.handler
      Role: !GetAtt LambdaRole.Arn
      Runtime: python3.12
      VpcConfig:
        SecurityGroupIds:
          - !GetAtt LambdaSecurityGroup.GroupId
        SubnetIds:
          - !Ref Subnet1
          - !Ref Subnet2
          - !Ref Subnet3

  LambdaPermissions:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !GetAtt LambdaFunction.Arn
      Principal: elasticloadbalancing.amazonaws.com

  LambdaTarget:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    DependsOn:
      - LambdaPermissions
    Properties:
      TargetType: lambda
      Targets:
        - Id: !GetAtt LambdaFunction.Arn

Sie werden aufgefordert, sich anzumelden, wenn Sie zum Load Balancer navigieren. Danach können Sie sehen, dass die Lambda-Funktion aufgerufen wurde. Dies ist ein sehr einfaches Beispiel, aber die Idee ist, dass Sie die Lambda-Funktion mit der Logik erweitern können, die Sie benötigen. Sie könnten zum Beispiel eine vorab signierte URL für den Bericht auf S3 erstellen und den Benutzer direkt zu dieser URL weiterleiten. Dadurch wird der Bericht automatisch heruntergeladen.

Fazit

Die Sicherung des Zugriffs auf Ihre S3-Dateien muss nicht kompliziert sein. Durch den Einsatz von AWS Cognito, einem Application Load Balancer und einer einfachen Lambda-Funktion können Sie genau kontrollieren, wer auf Ihre Dateien zugreifen darf - ohne sie der Öffentlichkeit preiszugeben. Mit dieser Einrichtung wird die Authentifizierung nahtlos gehandhabt, und die Autorisierung ist so einfach wie die Überprüfung von Gruppenmitgliedschaften. Von hier aus können Sie die Funktionalität weiter ausbauen, z. B. vorsignierte URLs für Downloads generieren oder noch detailliertere Berechtigungen hinzufügen. Die Cloud macht es Ihnen leicht - Sie müssen nur wissen, wie!

Foto von Pixabay

Verfasst von

Joris Conijn

Joris is the AWS Practise CTO of the Xebia Cloud service line and has been working with the AWS cloud since 2009 and focussing on building event-driven architectures. While working with the cloud from (almost) the start, he has seen most of the services being launched. Joris strongly believes in automation and infrastructure as code and is open to learning new things and experimenting with them because that is the way to learn and grow.

Contact

Let’s discuss how we can support your journey.