Cloud Computing
14 min di lettura

Come Accedere agli Oggetti S3 Privati con AWS Cognito

Guida per fornire accesso sicuro ai file privati con l'integrazione di pool utenti Cognito, API Gateway, Lambda e S3.

N
Necmettin Demir
21 luglio 2023
Caricamento...

Come Accedere agli Oggetti S3 Privati con AWS Cognito

AWS Cognito S3
AWS Cognito S3

Scenario

Supponiamo che stiate sviluppando alcune applicazioni per il vostro cliente. Tuttavia, ci sono alcuni file come PDF, Word, Excel, ecc. relativi ai record nelle applicazioni. Per semplicità dello scenario, supponiamo che questi file siano archiviati in un singolo bucket S3 privato (private) su AWS.
Gli utenti devono poter accedere a questi file correlati dal bucket S3 privato tramite un link URL nelle applicazioni. La nostra soluzione deve funzionare come soluzione portatile (portable) per qualsiasi software aziendale interno.

Introduzione

Lo scopo di questo articolo è mostrare come scaricare file dal bucket S3 privato utilizzando i pool utenti Cognito. Oltre a Cognito, viene mostrato il flusso da Cognito ad API Gateway con Authorizer e la collaborazione di API Gateway con Lambda.
Sono state condivise quante più schermate possibili dalla console AWS per ogni passaggio. Sono state aggiunte molte immagini per rendere i passaggi più chiari, specialmente per i principianti.

Background

Per comprendere meglio ciò che viene sviluppato in questo articolo, alcune letture preliminari potrebbero essere utili. I seguenti link saranno particolarmente utili per i principianti di AWS:

Cosa Fare?

Per un tale compito possono essere codificati molti flussi o metodi. Qui implementeremo il metodo mostrato di seguito. Una breve spiegazione di come implementare lo scenario è presentata nell'immagine seguente.
L'immagine seguente mostra che dobbiamo creare alcuni elementi come Pool Utenti Cognito, bucket S3, Metodi API Gateway, Funzioni Lambda, ecc. Dopo aver creato tutte le entità nell'ambiente AWS, dobbiamo configurarle correttamente affinché possano lavorare insieme in collaborazione.
Architettura del Sistema
Architettura del Sistema
È meglio creare tutti gli elementi nell'ambiente AWS in ordine inverso. Ad esempio, per utilizzare Lambda con un metodo API, se la funzione Lambda viene sviluppata prima, quando viene creato il metodo API Gateway, questa funzione può essere facilmente collegata. Allo stesso modo, nel Passaggio 5 dovremmo creare il bucket S3 web e inserirvi il file callback.html, in modo da poterlo utilizzare quando creiamo il Pool Utenti Cognito nel Passaggio 6. Naturalmente questo non è obbligatorio, ma questo ordine faciliterà lo sviluppo. Pertanto, questo approccio è stato preferito qui.

Bozza

Cercheremo le risposte alle seguenti domande. Ricordate che dovete avere un account AWS per implementare tutti i passaggi di questo articolo.
  1. Come Creare un Bucket S3 Privato?
  2. Come Creare una Policy Personalizzata per il Permesso di Accesso agli Oggetti nel Bucket S3 Privato?
  3. Come Creare una Funzione Lambda per Accedere agli Oggetti nel Bucket S3 Privato?
  4. Come Creare un Gateway API per Utilizzare la Funzione Lambda?
  5. Come Creare un Bucket S3 Pubblico da Usare come Cartella Web?
  6. Come Creare e Configurare un Pool Utenti Cognito?
  7. Come Testare lo Scenario?

1. Come Creare un Bucket S3 Privato?

S3 è uno dei servizi basati sulla regione in AWS. Gli elementi nei bucket S3 sono chiamati oggetti (object). Pertanto, in AWS i termini oggetto e file possono essere usati in modo intercambiabile per i bucket S3.
Mantenete selezionata la casella "Blocca Tutti gli Accessi Pubblici" (Block All Public Access). Qui è stato creato un bucket S3 privato. Sebbene ci siano molte opzioni di configurazione extra, lo creiamo con valori predefiniti per semplicità della soluzione.
Creazione Bucket S3
Creazione Bucket S3
Caricate alcuni oggetti nel bucket S3 per testare l'accesso privato. Successivamente, provate ad accedere a questi oggetti con utenti non autorizzati o possibili link di accesso. Sebbene conosciamo file PDF, DOC, XLS, ecc., nella terminologia AWS S3 tutti sono chiamati oggetti.
Caricamento File
Caricamento File

2. Creazione di una Policy per il Permesso di Accesso agli Oggetti nel Bucket S3 Privato

In AWS, IAM (Identity and Access Management) è la base di tutti i servizi! Utenti, Gruppi, Ruoli e Policy sono i concetti fondamentali con cui dobbiamo familiarizzare.
Ci sono molti ruoli integrati (built-in) e ogni ruolo ha molte policy integrate che significano permessi. Questi sono chiamati "AWS Managed". Tuttavia, è anche possibile creare ruoli e policy "Customer Managed" (Gestiti dal Cliente). Pertanto, qui è stata creata una policy personalizzata.
  • Create una policy IAM personalizzata per recuperare oggetti dal vostro bucket S3 privato.
  • Trovate l'elenco delle policy esistenti in AWS e createne una nuova per eseguire solo l'operazione GetObject dal vostro bucket S3 privato come mostrato di seguito:
Elenco Policy
Elenco Policy
Create una policy personalizzata come mostrato di seguito. Selezionate S3 come servizio e solo GetObject come azione (action):
Impostazioni Policy 1
Impostazioni Policy 1
Selezionate "specific" come risorsa (resource) e specificate il vostro bucket S3 privato affinché la policy abbia le capacità desiderate:
Impostazioni Policy 2
Impostazioni Policy 2
Date un nome alla vostra policy e createla. Potete dare qualsiasi nome ma dovrete ricordarlo.
Impostazioni Policy 3
Impostazioni Policy 3
Il riepilogo della vostra policy personalizzata apparirà come segue. È possibile creare la policy anche utilizzando direttamente questo contenuto JSON:
Policy JSON
Policy JSON
Definizione JSON della Policy:
JSON
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::private-s3-for-interfacing/*"
        }
    ]
}

3. Creazione di una Funzione Lambda per Accedere agli Oggetti nel Bucket S3 Privato

Qui è stata utilizzata l'ultima versione di NodeJS per la funzione Lambda. Create una funzione Lambda e selezionate NodeJS. È possibile selezionare qualsiasi linguaggio supportato come Python, Go, Java, .NET Core, ecc. per la funzione Lambda.
Creazione Lambda
Creazione Lambda
Quando create la funzione Lambda, viene mostrato un codice "hello" di esempio. Dobbiamo sviluppare il nostro codice al suo posto.
Come visibile, l'ambiente di sviluppo Lambda è simile a un IDE leggero basato sul web.
Modifica Codice Lambda
Modifica Codice Lambda
Sostituite il codice esistente con il breve codice di esempio fornito. La nuova versione del codice sarà come segue. Dopo aver modificato il codice, premete il pulsante "Deploy" per utilizzare la funzione Lambda.
Per semplicità dello scenario, il nome del bucket viene utilizzato staticamente. Il nome del file viene inviato come parametro con il nome fn. Sebbene il tipo di contenuto (content type) predefinito sia assunto come pdf, può essere qualsiasi tipo di file implementato nel codice della funzione Lambda. Poiché preferiremo utilizzare la funzionalità proxy della funzione Lambda nella connessione API Gateway, l'intestazione della risposta (response header) contiene alcuni dati aggiuntivi richiesti.
Codice Lambda NodeJS (ritorno come Blob):
JavaScript
// Il codice della funzione Lambda apparirà così
// Questo codice restituirà la risposta come contenuto blob
// Per scaricare il file può essere utilizzato Callback-to-Download-Blob.html negli allegati

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, // Chiave del successo
          '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
    }
  }
}
È anche possibile utilizzare codice Python nella funzione Lambda come mostrato di seguito:
Python
# Il codice seguente può essere sviluppato come l'esempio NodeJS sopra
    
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'
                         # È possibile aggiungere qui codici relativi a CORS se necessario
                        },
            'body': base64.b64encode(file).decode('utf-8'),           
            'isBase64Encoded': True
        }
    except:
        return {
            'headers': { 'Content-type': 'text/html' },
            'statusCode': 200,
            'body': 'Si è verificato un errore in Lambda!' 
        }
Un altro metodo potrebbe essere creare un presigned URL con Lambda:
JavaScript
// Questo metodo fornirà un presigned url
// Per utilizzare il link presigned URL può essere usato il file Callback-for-preSignedUrl.html

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 // secondi
  });

  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
    }    
};
Quando viene creata la funzione Lambda, viene creato un ruolo insieme ad essa. Tuttavia, questo ruolo non ha il permesso di accedere agli oggetti nel vostro bucket S3 privato. Ora dobbiamo allegare la policy "Customer Managed" che abbiamo creato nei passaggi precedenti a questo ruolo creato con la funzione Lambda.
Dopo aver creato la funzione Lambda, possiamo trovare il ruolo creato automaticamente come mostrato di seguito:
Trovare il Ruolo Lambda
Trovare il Ruolo Lambda
Allegate la policy personalizzata che avete creato nel passaggio precedente a questo ruolo; così la funzione Lambda avrà accesso limitato GetObject al vostro bucket S3.
Allegare Policy
Allegare Policy
Questo è tutto ciò che serve per l'accesso di Lambda al vostro bucket S3. Ora è il momento di creare un metodo AWS Gateway per utilizzare la nostra funzione Lambda.

4. Creazione di un Gateway API per Utilizzare la Funzione Lambda

Create un AWS Gateway REST API come mostrato di seguito. Come visibile, ci sono molte opzioni ma noi creiamo un'API "REST" come "New API". Date un nome al vostro API Gateway.
Creazione REST API
Creazione REST API
Ci sono alcuni passaggi per creare e far funzionare l'AWS GW API:
  • Creare API
  • Creare Resource
  • Creare Method
  • Distribuire (Deploy) l'API
Create una Resource per la vostra REST API come mostrato di seguito:
Creazione Resource Passo 1
Creazione Resource Passo 1
La risorsa (resource) creata qui sarà utilizzata successivamente nell'URL dell'API.
Creazione Resource Passo 2
Creazione Resource Passo 2
Create il metodo GET per la risorsa che avete creato:
Creazione Metodo GET
Creazione Metodo GET
Qui può essere creato qualsiasi metodo HTTP come GET, POST, PUT, DELETE, ecc. Per le nostre esigenze creiamo solo GET. Non dimenticate di collegare la funzione Lambda che abbiamo creato nei passaggi precedenti a questo metodo.
Lambda Proxy Integration è selezionato qui. Questo approccio ci permette di gestire tutti i contenuti relativi alla risposta nella Funzione Lambda.
Integrazione Lambda Proxy
Integrazione Lambda Proxy
Dopo che il metodo GET è stato creato, il flusso tra il Metodo API Gateway e la funzione Lambda apparirà come segue:
Vista del Flusso
Vista del Flusso
Abilitate CORS per il Gateway API come mostrato di seguito. Le opzioni Default 4xx e Default 5xx possono essere selezionate; così anche gli errori possono essere restituiti senza problemi.
Abilitazione CORS
Abilitazione CORS
Dopo aver creato e configurato tutto relativo al metodo AWS Gateway, è ora di distribuire (deploy) l'API. L'API viene distribuita in uno stage come mostrato. Anche questo nome dello stage sarà utilizzato nell'URL generale dell'API.
Distribuzione API
Distribuzione API
Dopo la distribuzione, l'URL apparirà come segue. Ora è possibile utilizzare questo link da qualsiasi applicazione.
URL di Distribuzione
URL di Distribuzione
Per limitare l'accesso all'API gateway, dobbiamo definire un Authorizer (Autorizzatore). Possiamo definire un Autorizzatore Cognito come mostrato di seguito.
Come visibile nell'immagine seguente, Authorization è il token JWT che deve essere aggiunto alla parte header della richiesta per utilizzare il metodo API autorizzato.
Quando l'UI Hosted di Cognito viene inviata con un utente/password Cognito, Cognito reindirizzerà l'utente all'URL di callback trasferendo id_token e dati state aggiuntivi.
Notate che il token che dobbiamo aggiungere all'header è chiamato "Authorization" sotto Token Source.
Definizione Cognito Authorizer
Definizione Cognito Authorizer
Dopo che l'Autorizzatore basato su Cognito è stato definito, può essere utilizzato come segue:
Utilizzo Authorizer
Utilizzo Authorizer
D'altra parte, se non volete definire un Authorizer per l'API Gateway, potete limitare l'accesso all'URL dell'API con "Resource Policy" (Policy delle Risorse) come mostrato di seguito.
Se la Resource Policy viene modificata/aggiunta, l'API deve essere ridistribuita. L'IP mostrato come xxx.xxx.xxx.xxx può essere l'IP del server. Quando qualcuno tenta di accedere all'URL da un IP diverso, verrà mostrato il seguente messaggio:
{"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"}
Impostazione Resource Policy
Impostazione Resource Policy
Il codice JSON della Resource Policy sarà come segue:
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 Pubblico da Usare come Cartella Web

Per la soluzione abbiamo bisogno di due bucket S3 (bucket). Il primo è stato creato nelle sezioni precedenti. Il secondo viene creato ora e sarà utilizzato come cartella web. Il primo era utilizzato come bucket privato per archiviare tutti i file.
Struttura dei Due Bucket S3
Struttura dei Due Bucket S3
Create un bucket S3 pubblico come cartella web. Questo bucket contiene un file callback.html, quindi può essere utilizzato come indirizzo di callback di Cognito.
Creazione Bucket Web
Creazione Bucket Web
Il bucket S3 per il web deve essere pubblico (public). Pertanto, può essere applicata la seguente policy:
JSON
// Il JSON della policy apparirà così

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

Scarica i File Sorgente

Puoi scaricare Callback.html e altri file sorgente dai seguenti link:

6. Creazione e Configurazione del Pool Utenti Cognito

  • Indirizzo callback: https://web-s3-for-interfacing.s3.eu-west-2.amazonaws.com/Callback.html
  • OAuth 2.0 Flows: selezionate l'opzione "implicit grant".
  • OAuth 2.0 Scopes: email, openid, profile.
Esaminate il link dell'UI hosted di seguito. Per inviare parametri alla pagina di login Cognito hosted, aggiungete un parametro URL "state" aggiuntivo. Il parametro "state" sarà passato al file Callback.html.
Il link dell'UI Cognito Hosted contiene molti parametri URL come mostrato di seguito:
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
Campi:
  • 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 è un parametro URL personalizzato. Può essere inviato alla pagina UI Hosted e restituito alla pagina Callback.html.
Deve essere creata un'app client come mostrato di seguito:
Creazione App Client
Creazione App Client
Le impostazioni dell'App client possono essere confermate come mostrato di seguito:
Impostazioni App Client
Impostazioni App Client
Deve essere impostato un nome di dominio (domain name) per poterlo utilizzare come URL per l'UI Hosted.
Impostazione Dominio
Impostazione Dominio

7. Come Testare lo Scenario?

Vediamo come testare l'API che permette accesso limitato utilizzando il Pool Utenti Cognito.
Qualsiasi utente finale può cliccare su un link per avviare questo processo. Supponiamo di avere una pagina web che ospita il seguente contenuto HTML. Come visibile, per ogni file il link è l'URL dell'UI hosted di Cognito.
Il file LinkToS3Files.html può essere utilizzato per testare lo scenario.

Scarica i File di Test


Conclusione

Spero che questo articolo sia stato utile per i principianti nell'ambiente cloud AWS.

Servizi di Cloud Computing

Offriamo servizi di progettazione dell'infrastruttura, migrazione, gestione e ottimizzazione su piattaforme AWS, Azure e Google Cloud.

Scopri il Nostro Servizio

Contattaci

Contatta il nostro team per informazioni dettagliate sulle nostre soluzioni AWS e cloud computing.

Contatti