Cloud Computing
15 min de lecture

Comment Accéder aux Objets S3 Privés avec AWS Cognito

Guide pour fournir un accès sécurisé aux fichiers privés avec l'intégration des pools d'utilisateurs Cognito, API Gateway, Lambda et S3.

N
Necmettin Demir
21 juillet 2023
Chargement...

Comment Accéder aux Objets S3 Privés avec AWS Cognito

AWS Cognito S3
AWS Cognito S3

Scénario

Supposons que vous développez certaines applications pour votre client. Cependant, il existe des fichiers tels que PDF, Word, Excel, etc. liés aux enregistrements dans les applications. Pour la simplicité du scénario, supposons que ces fichiers sont stockés dans un seul bucket S3 privé sur AWS.
Les utilisateurs doivent pouvoir accéder à ces fichiers concernés depuis le bucket S3 privé via un lien URL dans les applications. Notre solution doit fonctionner comme une solution portable pour tout logiciel interne à l'entreprise.

Introduction

L'objectif de cet article est de montrer comment télécharger des fichiers depuis un bucket S3 privé en utilisant les pools d'utilisateurs Cognito. En plus de Cognito, le flux de Cognito vers API Gateway avec Authorizer et la collaboration entre API Gateway et Lambda sont démontrés.
Autant de captures d'écran que possible ont été partagées pour chaque étape depuis la console AWS. De nombreuses images ont été ajoutées pour rendre les étapes plus claires, en particulier pour les débutants.

Contexte

Quelques lectures préalables peuvent être utiles pour mieux comprendre ce qui est développé dans cet article. Les liens suivants seront particulièrement utiles pour les débutants sur AWS :

Que Faut-il Faire ?

De nombreux flux ou méthodes peuvent être codés pour une telle tâche. Ici, nous allons implémenter la méthode montrée ci-dessous. Une brève description de la façon dont le scénario sera implémenté est présentée dans l'image suivante.
L'image suivante montre que nous devons créer certains éléments tels que le Pool d'Utilisateurs Cognito, les buckets S3, les Méthodes API Gateway, les Fonctions Lambda, etc. Après avoir créé toutes les entités dans l'environnement AWS, nous devons les configurer correctement pour qu'elles puissent toutes fonctionner ensemble en collaboration.
Architecture du Système
Architecture du Système
Il est préférable de créer tous les éléments dans l'environnement AWS dans l'ordre inverse. Par exemple, pour utiliser Lambda avec une méthode API, si la fonction Lambda est d'abord développée, elle peut facilement être liée lorsque la méthode API Gateway est créée. De même, nous devons créer le bucket web S3 à l'étape 5 et y placer le fichier callback.html, afin de pouvoir utiliser ce fichier lors de la création du Pool d'Utilisateurs Cognito à l'étape 6. Bien sûr, ce n'est pas obligatoire, mais cet ordre facilitera le développement. C'est pourquoi cette approche est préférée ici.

Plan

Nous allons chercher les réponses aux questions suivantes. N'oubliez pas que vous devez avoir un compte AWS pour appliquer toutes les étapes de cet article.
  1. Comment Créer un Bucket S3 Privé ?
  2. Comment Créer une Politique Personnalisée pour Autoriser l'Accès aux Objets du Bucket S3 Privé ?
  3. Comment Créer une Fonction Lambda pour Accéder aux Objets du Bucket S3 Privé ?
  4. Comment Créer une API Gateway pour Utiliser la Fonction Lambda ?
  5. Comment Créer un Bucket S3 Public pour l'Utiliser comme Dossier Web ?
  6. Comment Créer et Configurer un Pool d'Utilisateurs Cognito ?
  7. Comment Tester le Scénario ?

1. Comment Créer un Bucket S3 Privé ?

S3 est l'un des services basés sur les régions dans AWS. Les éléments dans les buckets S3 sont appelés objets. Par conséquent, dans AWS, les termes objet et fichier peuvent être utilisés de manière interchangeable pour les buckets S3.
Gardez la case "Bloquer tout accès public" cochée. Un bucket S3 privé a été créé ici. Bien qu'il existe de nombreuses options de configuration supplémentaires, nous le créons avec les valeurs par défaut pour la simplicité de la solution.
Création du Bucket S3
Création du Bucket S3
Téléchargez quelques objets dans le bucket S3 pour tester l'accès privé. Ensuite, essayez d'accéder à ces objets avec des utilisateurs non autorisés ou des liens d'accès potentiels. Bien que nous connaissions les fichiers PDF, DOC, XLS, etc., dans la terminologie AWS S3, ils sont tous appelés objets.
Téléchargement de Fichiers
Téléchargement de Fichiers

2. Création d'une Politique pour Autoriser l'Accès aux Objets du Bucket S3 Privé

Dans AWS, IAM (Identity and Access Management) est la base de tous les services ! Les Utilisateurs, Groupes, Rôles et Politiques sont les concepts de base que nous devons connaître.
Il existe de nombreux rôles intégrés et chaque rôle a de nombreuses politiques intégrées qui signifient des permissions. Ceux-ci sont appelés "AWS Managed". Cependant, il est également possible de créer des rôles et politiques "Customer Managed" (Gérés par le Client). Ainsi, une politique personnalisée a été créée ici.
  • Créez une politique IAM personnalisée pour récupérer des objets depuis votre bucket S3 privé.
  • Trouvez la liste des politiques existantes dans AWS et créez-en une nouvelle pour effectuer uniquement l'opération GetObject depuis votre bucket S3 privé comme montré ci-dessous :
Liste des Politiques
Liste des Politiques
Créez une politique personnalisée comme montré ci-dessous. Sélectionnez S3 comme service et uniquement GetObject comme action :
Paramètres de la Politique 1
Paramètres de la Politique 1
Sélectionnez "specific" comme ressource et spécifiez votre bucket S3 privé pour que la politique ait les capacités souhaitées :
Paramètres de la Politique 2
Paramètres de la Politique 2
Donnez un nom à votre politique et créez-la. Vous pouvez donner n'importe quel nom, mais vous devrez vous en souvenir.
Paramètres de la Politique 3
Paramètres de la Politique 3
Le résumé de votre politique personnalisée ressemblera à ceci. Il est également possible de créer une politique en utilisant directement ce contenu JSON :
JSON de la Politique
JSON de la Politique
Définition JSON de la Politique :
JSON
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::private-s3-for-interfacing/*"
        }
    ]
}

3. Création d'une Fonction Lambda pour Accéder aux Objets du Bucket S3 Privé

La dernière version de NodeJS a été utilisée ici pour la fonction Lambda. Créez une fonction Lambda et sélectionnez NodeJS. Vous pouvez choisir n'importe quel langage supporté pour la fonction Lambda comme Python, Go, Java, .NET Core, etc.
Création Lambda
Création Lambda
Lorsque vous créez la fonction Lambda, un exemple de code "hello" est affiché. Nous devons plutôt développer notre propre code.
Comme on peut le voir, l'environnement de développement Lambda ressemble à un IDE léger basé sur le web.
Modification du Code Lambda
Modification du Code Lambda
Remplacez le code existant par le court exemple de code fourni. Le nouveau code ressemblera à ceci. Après avoir modifié le code, appuyez sur le bouton "Deploy" pour utiliser la fonction Lambda.
Pour la simplicité du scénario, le nom du bucket est utilisé de manière statique. Le nom du fichier est envoyé comme paramètre avec le nom fn. Bien que le type de contenu par défaut soit supposé être pdf, il peut s'agir de n'importe quel type de fichier implémenté dans le code de la fonction Lambda. Comme nous préférerons utiliser la fonctionnalité proxy de la fonction Lambda dans la connexion API Gateway, l'en-tête de réponse contient certaines données supplémentaires requises.
Code Lambda NodeJS (retour en Blob) :
JavaScript
// Lambda fonksiyonu kodu bu şekilde görünür
// Bu kod yanıtı blob içeriği olarak döndürecektir
// Dosyayı indirmek için eklentilerdeki Callback-to-Download-Blob.html kullanılabilir

const AWS = require('aws-sdk');
const S3= new AWS.S3();
exports.handler = async (event, context) => {
    
  let fileName;
  let bucketName;
  let contentType;
  let fileExt;
    
  try {
    bucketName = 'private-s3-for-interfacing';
    fileName = event["queryStringParameters"]['fn']
    contentType = 'application/pdf';
    fileExt = 'pdf';
    
    //------------
    fileExt = fileName.split('.').pop();
    
    switch (fileExt) {
        case 'pdf': contentType = 'application/pdf'; break;        
        case 'png': contentType = 'image/png'; break;
        case 'gif': contentType = 'image/gif'; break;
        case 'jpeg': case 'jpg': contentType = 'image/jpeg'; break;
        case 'svg': contentType = 'image/svg+xml'; break;
        case 'docx': contentType = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'; break;
        case 'xlsx': contentType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; break;
        case 'pptx': contentType = 'application/vnd.openxmlformats-officedocument.presentationml.presentation'; break;
        case 'doc': contentType = 'application/msword'; break;
        case 'xls': contentType = 'application/vnd.ms-excel'; break;
        case 'csv': contentType = 'text/csv'; break;
        case 'ppt': contentType = 'application/vnd.ms-powerpoint'; break;
        case 'rtf': contentType = 'application/rtf'; break;
        case 'zip': contentType = 'application/zip'; break;
        case 'rar': contentType = 'application/vnd.rar'; break;
        case '7z': contentType = 'application/x-7z-compressed'; break;
        default: ;
    }
    
    //------------
    const data = await S3.getObject({Bucket: bucketName, Key: fileName}).promise();
    
    return {
       headers: {
          'Content-Type': contentType,
          'Content-Disposition': 'attachment; filename=' + fileName, // Başarının anahtarı
          'Content-Encoding': 'base64',
          'Access-Control-Allow-Origin': '*',
          'Access-Control-Allow-Headers': 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token', 
          'Access-Control-Allow-Methods': 'GET,OPTIONS'
      },
      body: data.Body.toString('base64'),
      isBase64Encoded: true,
      statusCode: 200
    }
  } catch (err) {
    return {
      statusCode: err.statusCode || 400,
      body: err.message || JSON.stringify(err.message) + ' - fileName: '+ fileName + ' - bucketName: ' + bucketName
    }
  }
}
Il est également possible d'utiliser du code Python dans la fonction Lambda comme montré ci-dessous :
Python
# Aşağıdaki kod yukarıdaki NodeJS örneği gibi geliştirilebilir
    
import base64
import boto3
import json
import random

s3 = boto3.client('s3')

def lambda_handler(event, context):
    try:
        fileName = event['queryStringParameters']['fn']
        bucketName = 'private-s3-for-interfacing'        
        contentType = 'application/pdf'
        
        response = s3.get_object(
            Bucket=bucketName,
            Key=fileName,
        )
        
        file = response['Body'].read()
        
        return {
            'statusCode': 200,
            'headers': {  
                         'Content-Type': contentType,                            
                         'Content-Disposition': 'attachment; filename='+ fileName,
                         'Content-Encoding': 'base64'
                         # Gerekirse buraya CORS ile ilgili kodlar eklenebilir
                        },
            'body': base64.b64encode(file).decode('utf-8'),           
            'isBase64Encoded': True
        }
    except:
        return {
            'headers': { 'Content-type': 'text/html' },
            'statusCode': 200,
            'body': 'Error occurred in Lambda!' 
        }
Une autre méthode pourrait être de créer une presigned URL avec Lambda :
JavaScript
// Bu yöntem presigned url sağlayacaktır
// Presigned URL bağlantısını kullanmak için Callback-for-preSignedUrl.html dosyası kullanılabilir

var AWS = require('aws-sdk');
var S3 = new AWS.S3({
  signatureVersion: 'v4',
});

exports.handler = async (event, context) => {
    
  let fileName;
  let bucketName;
  let contentType;
    
  bucketName = 'private-s3-for-interfacing';
  fileName = event["queryStringParameters"]['fn'];
  contentType = 'application/json';
    
  const presignedUrl = S3.getSignedUrl('getObject', {
    Bucket: bucketName,
    Key: fileName,
    Expires: 300 // saniye
  });

  let responseBody = {'presignedUrl': presignedUrl};
  
  return {
       headers: {
          'Content-Type': contentType,
          'Access-Control-Allow-Origin': '*',
          'Access-Control-Allow-Headers': 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token', 
          'Access-Control-Allow-Methods': 'GET,OPTIONS'
      },
      body: JSON.stringify(responseBody), 
      statusCode: 200
    }    
};
Lorsque la fonction Lambda est créée, un rôle est créé avec elle. Cependant, ce rôle n'a pas la permission d'accéder aux objets de votre bucket S3 privé. Maintenant, nous devons attacher la politique "Customer Managed" que nous avons créée dans les étapes précédentes à ce rôle créé avec la fonction Lambda.
Après avoir créé la fonction Lambda, nous pouvons trouver le rôle automatiquement créé comme montré ci-dessous :
Trouver le Rôle Lambda
Trouver le Rôle Lambda
Attachez la politique personnalisée que vous avez créée à l'étape précédente à ce rôle ; ainsi, la fonction Lambda aura un droit d'accès GetObject restreint sur votre bucket S3.
Attacher la Politique
Attacher la Politique
C'est tout ce qui doit être fait pour l'accès de Lambda à votre bucket S3. Maintenant, il est temps de créer une méthode AWS Gateway pour utiliser notre fonction Lambda.

4. Création d'une API Gateway pour Utiliser la Fonction Lambda

Créez une AWS Gateway REST API comme montré ci-dessous. On peut voir qu'il y a de nombreuses options, mais nous créons une API "REST" comme "New API". Donnez un nom à votre API Gateway.
Création de l'API REST
Création de l'API REST
Il y a quelques étapes pour créer et exécuter l'AWS GW API :
  • Créer l'API
  • Créer la Resource
  • Créer la Method
  • Déployer l'API
Créez une Resource pour votre REST API comme montré ci-dessous :
Création de la Resource Étape 1
Création de la Resource Étape 1
La ressource créée ici sera plus tard utilisée dans l'URL de l'API.
Création de la Resource Étape 2
Création de la Resource Étape 2
Créez une méthode GET pour la ressource que vous avez créée :
Création de la Méthode GET
Création de la Méthode GET
Ici, n'importe quelle méthode HTTP comme GET, POST, PUT, DELETE, etc. peut être créée. Pour notre besoin, nous ne créons que GET. N'oubliez pas de lier la fonction Lambda que nous avons créée dans les étapes précédentes à cette méthode.
Lambda Proxy Integration est cochée ici. Cette approche nous permet de traiter tout le contenu lié à la réponse dans la Fonction Lambda.
Intégration Proxy Lambda
Intégration Proxy Lambda
Après la création de la méthode GET, le flux entre la Méthode API Gateway et la fonction Lambda ressemblera à ceci :
Vue du Flux
Vue du Flux
Activez CORS pour l'API Gateway comme montré ci-dessous. Les options Default 4xx et Default 5xx peuvent être cochées ; ainsi, même les erreurs peuvent être retournées sans problème.
Activation de CORS
Activation de CORS
Après avoir créé et configuré tout ce qui concerne la méthode AWS Gateway, il est maintenant temps de déployer l'API. L'API est déployée sur un stage comme montré. De plus, ce nom de stage sera utilisé dans l'URL publique de l'API.
Déploiement de l'API
Déploiement de l'API
Après le déploiement, l'URL ressemblera à ceci. Il est maintenant possible d'utiliser ce lien depuis n'importe quelle application.
URL de Déploiement
URL de Déploiement
Pour restreindre l'accès à l'API gateway, nous devons définir un Authorizer (Autorisateur). Nous pouvons définir un Authorizer Cognito comme montré ci-dessous.
Comme on peut le voir dans l'image suivante, l'Authorization est le jeton JWT qui doit être ajouté à la section header de la requête pour utiliser la méthode API autorisée.
Lorsque la Hosted UI Cognito est envoyée avec un utilisateur/mot de passe Cognito, Cognito redirigera l'utilisateur vers l'URL de callback en passant le id_token et des données state additionnelles.
Notez que le jeton que nous devons ajouter à la section header est nommé "Authorization" sous Token Source.
Définition de l'Authorizer Cognito
Définition de l'Authorizer Cognito
Après que l'Authorizer basé sur Cognito est défini, il peut être utilisé comme ci-dessous :
Utilisation de l'Authorizer
Utilisation de l'Authorizer
D'autre part, si vous ne voulez pas définir d'Authorizer pour l'API Gateway, vous pouvez restreindre l'accès à l'URL de l'API avec une "Resource Policy" (Politique de Ressource) comme montré ci-dessous.
Si la Resource Policy est modifiée/ajoutée, l'API doit être redéployée. L'IP montrée comme xxx.xxx.xxx.xxx peut être l'IP du serveur. Lorsque quelqu'un essaie d'accéder à l'URL depuis une IP différente, le message suivant sera affiché :
{"Message":"User: anonymous is not authorized to perform: execute-api:Invoke on resource: arn:aws:execute-api:eu-west-2:********8165:... with an explicit deny"}
Configuration de la Resource Policy
Configuration de la Resource Policy
Le code JSON de la Resource Policy sera comme suit :
JSON
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": "execute-api:Invoke",
            "Resource": "*"
        },
        {
            "Effect": "Deny",
            "Principal": "*",
            "Action": "execute-api:Invoke",
            "Resource": "*",
            "Condition": {
                "NotIpAddress": {
                    "aws:SourceIp": "xxx.xxx.xxx.xxx"
                }
            }
        }
    ]
}

5. Bucket S3 Public pour Utilisation comme Dossier Web

Nous avons besoin de deux buckets S3 pour la solution. Le premier a été créé dans les sections précédentes. Le second est maintenant créé et sera utilisé comme dossier web. Le premier était utilisé comme bucket privé pour stocker tous les fichiers.
Structure des Deux Buckets S3
Structure des Deux Buckets S3
Créez un bucket S3 public comme dossier web. Ce bucket contient un fichier callback.html, afin qu'il puisse être utilisé comme adresse de callback Cognito.
Création du Bucket Web
Création du Bucket Web
Le bucket S3 pour le web doit être public. Par conséquent, la politique suivante peut être appliquée :
JSON
// Politika JSON'ı bu şekilde görünecektir

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadGetObject",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::web-s3-for-interfacing/*"
        }
    ]
}

Télécharger les Fichiers Source

Vous pouvez télécharger Callback.html et d'autres fichiers source depuis les liens suivants :

6. Création et Configuration du Pool d'Utilisateurs Cognito

  • Adresse de callback : https://web-s3-for-interfacing.s3.eu-west-2.amazonaws.com/Callback.html
  • Flux OAuth 2.0 : cochez l'option "implicit grant".
  • Scopes OAuth 2.0 : email, openid, profile.
Examinez le lien hosted UI ci-dessous. Pour envoyer un paramètre à la page de connexion Cognito hosted, ajoutez un paramètre URL "state" supplémentaire. Le paramètre "state" sera transmis au fichier Callback.html.
Le lien de la Hosted UI Cognito contient de nombreux paramètres URL comme montré ci-dessous :
https://test-for-user-pool-for-s3.auth.eu-west-2.amazoncognito.com/login?client_id=7uuggclp7269oguth08mi2ee04&response_type=token&scope=openid+profile+email&redirect_uri=https://web-s3-for-interfacing.s3.eu-west-2.amazonaws.com/Callback.html&state=fn=testFile.pdf
Champs :
  • client_id=7uuggclp7269oguth08mi2ee04
  • response_type=token
  • scope=openid+profile+email
  • redirect_uri=https://web-s3-for-interfacing.s3.eu-west-2.amazonaws.com/Callback.html
  • state=fn=testFile.pdf
state est un paramètre URL spécial. Il peut être envoyé à la page hosted UI et retourné à la page Callback.html.
Une app client doit être créée comme montré ci-dessous :
Création de l'App Client
Création de l'App Client
Les paramètres de l'app client peuvent être confirmés comme montré ci-dessous :
Paramètres de l'App Client
Paramètres de l'App Client
Un nom de domaine doit être défini pour être utilisé comme URL pour la hosted UI.
Configuration du Domaine
Configuration du Domaine

7. Comment Tester le Scénario ?

Voyons comment tester l'API avec accès restreint en utilisant le Pool d'Utilisateurs Cognito.
N'importe quel utilisateur final peut cliquer sur un lien pour démarrer ce processus. Supposons que nous ayons une page web hébergeant le contenu HTML suivant. Comme on peut le voir, le lien pour chaque fichier est l'URL de la hosted UI Cognito.
Le fichier LinkToS3Files.html peut être utilisé pour tester le scénario.

Télécharger les Fichiers de Test


Conclusion

J'espère que cet article a été utile pour les débutants dans l'environnement cloud AWS.

Services de Cloud Computing

Nous offrons des services de conception d'infrastructure, migration, gestion et optimisation sur les plateformes AWS, Azure et Google Cloud.

Découvrir Notre Service

Contactez-nous

Contactez notre équipe pour des informations détaillées sur nos solutions AWS et cloud computing.

Contact