Come Accedere agli Oggetti S3 Privati con AWS Cognito

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.

È 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.
- Come Creare un Bucket S3 Privato?
- Come Creare una Policy Personalizzata per il Permesso di Accesso agli Oggetti nel Bucket S3 Privato?
- Come Creare una Funzione Lambda per Accedere agli Oggetti nel Bucket S3 Privato?
- Come Creare un Gateway API per Utilizzare la Funzione Lambda?
- Come Creare un Bucket S3 Pubblico da Usare come Cartella Web?
- Come Creare e Configurare un Pool Utenti Cognito?
- 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.

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.

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
GetObjectdal vostro bucket S3 privato come mostrato di seguito:

Create una policy personalizzata come mostrato di seguito. Selezionate S3 come servizio e solo
GetObject come azione (action):
Selezionate "specific" come risorsa (resource) e specificate il vostro bucket S3 privato affinché la policy abbia le capacità desiderate:

Date un nome alla vostra policy e createla. Potete dare qualsiasi nome ma dovrete ricordarlo.

Il riepilogo della vostra policy personalizzata apparirà come segue. È possibile creare la policy anche utilizzando direttamente questo contenuto 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.

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.

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:

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.
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.

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:
La risorsa (resource) creata qui sarà utilizzata successivamente nell'URL dell'API.

Create il metodo
GET per la risorsa che avete creato:
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.

Dopo che il metodo
GET è stato creato, il flusso tra il Metodo API Gateway e la funzione Lambda apparirà come segue:
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.

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.

Dopo la distribuzione, l'URL apparirà come segue. Ora è possibile utilizzare questo link da qualsiasi applicazione.

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.

Dopo che l'Autorizzatore basato su Cognito è stato definito, può essere utilizzato come segue:

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"}
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.

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.
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.pdfCampi:
client_id=7uuggclp7269oguth08mi2ee04response_type=tokenscope=openid+profile+emailredirect_uri=https://web-s3-for-interfacing.s3.eu-west-2.amazonaws.com/Callback.htmlstate=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:

Le impostazioni dell'App client possono essere confermate come mostrato di seguito:

Deve essere impostato un nome di dominio (domain name) per poterlo utilizzare come URL per l'UI Hosted.

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 ServizioContattaci
Contatta il nostro team per informazioni dettagliate sulle nostre soluzioni AWS e cloud computing.
Contatti