AWS Cognito ile Özel S3 Nesnelerine Nasıl Erişilir

Senaryo
Müşteriniz için bazı uygulamalar geliştirdiğinizi varsayalım. Ancak, uygulamalardaki kayıtlarla ilgili PDF, Word, Excel vb. bazı dosyalar bulunmaktadır. Senaryonun basitliği için, bu dosyaların AWS'deki tek bir özel (private) S3 kovasında (bucket) saklandığını varsayalım.
Kullanıcıların, bu ilgili dosyalara uygulamalardaki bir URL bağlantısı aracılığıyla özel S3 kovasından erişebilmeleri gerekmektedir. Çözümümüzün, şirket içi herhangi bir yazılım için taşınabilir (portable) bir çözüm olarak çalışması gerekmektedir.
Giriş
Bu makalenin amacı, Cognito kullanıcı havuzlarını (user pools) kullanarak özel S3 kovasındaki dosyaların nasıl indirileceğini göstermektir. Cognito'nun yanı sıra, Cognito'dan Yetkilendiricili (Authorizer) API Gateway'e giden akış ve API Gateway'in Lambda ile iş birliği gösterilmektedir.
AWS konsolundan her adım için mümkün olduğunca fazla ekran görüntüsü paylaşılmıştır. Özellikle yeni başlayanlar için adımların daha net olması adına birçok görsel eklenmiştir.
Arka Plan
Bu makalede geliştirilenleri daha iyi anlamak için bazı ön okumalar faydalı olabilir. Özellikle AWS'ye yeni başlayanlar için aşağıdaki bağlantılar yararlı olacaktır:
Ne Yapılmalı?
Böyle bir görev için birçok akış veya yöntem kodlanabilir. Burada, aşağıda gösterilen yöntemi uygulayacağız. Senaryonun nasıl uygulanacağına dair kısa bir açıklama aşağıdaki görselde sunulmuştur.
Aşağıdaki görsel; Cognito Kullanıcı Havuzu, S3 kovaları, API Gateway Metotları, Lambda Fonksiyonları vb. gibi bazı öğeler oluşturmamız gerektiğini göstermektedir. AWS ortamında tüm varlıkları oluşturduktan sonra, hepsinin birlikte iş birliği içinde çalışabilmesi için uygun şekilde yapılandırmamız gerekir.

AWS ortamındaki tüm öğeleri ters sırayla oluşturmak daha iyidir. Örneğin, Lambda'yı bir API metoduyla kullanmak için, önce Lambda fonksiyonu geliştirilirse, API Gateway metodu oluşturulduğunda bu fonksiyon kolayca bağlanabilir. Benzer şekilde, Adım 5'te S3 web kovasını oluşturmalı ve içine
callback.html dosyasını koymalıyız ki, Adım 6'da Cognito Kullanıcı Havuzunu oluştururken bu dosyayı kullanabilelim. Elbette bu zorunlu değildir, ancak bu sıra geliştirmeyi kolaylaştıracaktır. Bu nedenle burada bu yaklaşım tercih edilmiştir.Taslak
Aşağıdaki soruların yanıtlarını arayacağız. Bu makaledeki tüm adımları uygulamak için bir AWS hesabınızın olması gerektiğini unutmayın.
- Özel S3 Kovası (Bucket) Nasıl Oluşturulur?
- Özel S3 Kovasındaki Nesnelere Erişim İzni İçin Özel Bir Politika Nasıl Oluşturulur?
- Özel S3 Kovasındaki Nesnelere Erişmek İçin Bir Lambda Fonksiyonu Nasıl Oluşturulur?
- Lambda Fonksiyonunu Kullanmak İçin Gateway API Nasıl Oluşturulur?
- Web Klasörü Olarak Kullanmak İçin Genel S3 Kovası Nasıl Oluşturulur?
- Cognito Kullanıcı Havuzu Nasıl Oluşturulur ve Ayarları Nasıl Yapılandırılır?
- Senaryo Nasıl Test Edilir?
1. Özel S3 Kovası Nasıl Oluşturulur?
S3, AWS'deki bölge tabanlı (region-based) servislerden biridir. S3 kovalarındaki öğelere nesne (object) denir. Bu nedenle, AWS'de S3 kovaları için nesne ve dosya terimleri birbirinin yerine kullanılabilir.
"Tüm Genel Erişimi Engelle" (Block All Public Access) onay kutusunu işaretli tutun. Burada özel bir S3 kovası oluşturulmuştur. Birçok ekstra yapılandırma seçeneği olmasına rağmen, çözümün basitliği adına varsayılan değerlerle oluşturuyoruz.

S3 kovasına özel erişimi test etmek için içine bazı nesneler yükleyin. Daha sonra, bu nesnelere izin verilmeyen kullanıcılar veya olası erişim bağlantılarıyla erişmeye çalışın. Her ne kadar PDF, DOC, XLS vb. dosyaları bilsek de, AWS S3 terminolojisinde bunların hepsine nesne denir.

2. Özel S3 Kovasındaki Nesnelere Erişim İzni İçin Politika Oluşturma
AWS'de IAM (Kimlik ve Erişim Yönetimi) tüm servislerin temelidir! Kullanıcılar, Gruplar, Roller ve Politikalar aşina olmamız gereken temel kavramlardır.
Birçok yerleşik (built-in) rol vardır ve her rolün izin anlamına gelen birçok yerleşik politikası bulunur. Bunlara "AWS Managed" denir. Ancak, "Customer Managed" (Müşteri Yönetimli) roller ve politikalar oluşturmak da mümkündür. Dolayısıyla, burada özel bir politika oluşturulmuştur.
- Özel S3 kovanızdan nesne çekmek için özel bir IAM politikası oluşturun.
- AWS'deki mevcut politika listesini bulun ve aşağıda gösterildiği gibi yalnızca özel S3 kovanızdan
GetObjectişlemini gerçekleştirmek üzere yeni bir tane oluşturun:

Aşağıda gösterildiği gibi özel bir politika oluşturun. Servis olarak S3'ü ve işlem (action) olarak yalnızca
GetObject'i seçin:
Kaynak (resource) olarak "specific" seçin ve politikanın istediğiniz yeteneklere sahip olması için özel S3 kovanızı belirleyin:

Politikanıza bir ad verin ve oluşturun. Herhangi bir isim verebilirsiniz ancak bunu hatırlamanız gerekecektir.

Özel politikanızın özeti aşağıdaki gibi görünecektir. Bu JSON içeriğini doğrudan kullanarak da politika oluşturmak mümkündür:

Politika JSON Tanımı:
JSON
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::private-s3-for-interfacing/*"
}
]
}
3. Özel S3 Kovasındaki Nesnelere Erişmek İçin Lambda Fonksiyonu Oluşturma
Burada Lambda fonksiyonu için NodeJS'in son sürümü kullanılmıştır. Bir Lambda fonksiyonu oluşturun ve NodeJS'i seçin. Lambda fonksiyonu için Python, Go, Java, .NET Core vb. desteklenen herhangi bir dili seçmeniz mümkündür.

Lambda fonksiyonu oluşturduğunuzda, örnek bir "hello" kodu gösterilir. Bunun yerine kendi kodumuzu geliştirmemiz gerekiyor.
Görüldüğü gibi Lambda geliştirme ortamı, web tabanlı hafif bir IDE'ye benzemektedir.

Mevcut kodu verilen kısa örnek kodla değiştirin. Kodun yeni hali aşağıdaki gibi olacaktır. Kodu değiştirdikten sonra Lambda fonksiyonunu kullanmak için "Deploy" butonuna basın.
Senaryonun basitliği için kova adı statik olarak kullanılmıştır. Dosya adı,
fn adıyla parametre olarak gönderilir. Varsayılan içerik tipi (content type) pdf olarak kabul edilse de, Lambda fonksiyon kodunda uygulanan herhangi bir dosya tipi olabilir. API Gateway bağlantısında Lambda fonksiyonunun proxy özelliğini kullanmayı tercih edeceğimiz için, yanıt başlığı (response header) gereken bazı ek verileri içermektedir.NodeJS Lambda Kodu (Blob olarak döndürme):
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
}
}
}
Lambda fonksiyonunda aşağıda gösterildiği gibi Python kodu kullanmak da mümkündür:
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!'
}
Başka bir yöntem ise Lambda ile presigned URL oluşturmak olabilir:
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
}
};
Lambda fonksiyonu oluşturulduğunda, onunla birlikte bir rol oluşturulur. Ancak bu rolün özel S3 kovanızdaki nesnelere erişim izni yoktur. Şimdi, önceki adımlarda oluşturduğumuz "Customer Managed" politikayı Lambda fonksiyonuyla oluşturulan bu role eklememiz gerekiyor.
Lambda fonksiyonunu oluşturduktan sonra, aşağıda gösterildiği gibi otomatik olarak oluşturulan rolü bulabiliriz:

Önceki adımda oluşturduğunuz özel politikayı bu role ekleyin; böylece Lambda fonksiyonu S3 kovanız üzerinde kısıtlı
GetObject erişim hakkına sahip olacaktır.
Lambda'nın S3 kovanıza erişimi için yapılması gerekenler bu kadar. Şimdi, Lambda fonksiyonumuzu kullanmak için bir AWS Gateway metodu oluşturma zamanı.
4. Lambda Fonksiyonunu Kullanmak İçin Gateway API Oluşturma
Aşağıda gösterildiği gibi AWS Gateway REST API oluşturun. Birçok seçenek olduğu görülmektedir ancak biz bir "REST" API'yi "New API" olarak oluşturuyoruz. API Gateway'inize bir ad verin.

AWS GW API'yi oluşturmak ve çalıştırmak için bazı adımlar vardır:
- API Oluştur
- Resource Oluştur
- Method Oluştur
- API'yi Dağıt (Deploy)
REST API'niz için aşağıda gösterildiği gibi bir
Resource oluşturun:
Burada oluşturulan kaynak (resource), daha sonra API'nin URL'sinde kullanılacaktır.

Oluşturduğunuz kaynak için
GET metodu oluşturun:
Burada
GET, POST, PUT, DELETE vb. herhangi bir HTTP metodu oluşturulabilir. İhtiyacımız için yalnızca GET oluşturuyoruz. Önceki adımlarda oluşturduğumuz Lambda fonksiyonunu bu metotla bağlamayı unutmayın.Lambda Proxy Integration burada işaretlenmiştir. Bu yaklaşım, tüm yanıtla ilgili içerikleri Lambda Fonksiyonunda işlememizi sağlar.

GET metodu oluşturulduktan sonra, API Gateway Metodu ve Lambda fonksiyonu arasındaki akış aşağıdaki gibi görünecektir:
Aşağıda gösterildiği gibi Gateway API için CORS'u etkinleştirin. Default 4xx ve Default 5xx seçenekleri işaretlenebilir; böylece hatalar bile sorunsuz şekilde dönebilir.

AWS Gateway metoduyla ilgili her şeyi oluşturup yapılandırdıktan sonra, artık API'yi dağıtma (deploy) zamanıdır. API, gösterildiği gibi bir aşamaya (stage) dağıtılır. Ayrıca bu aşama adı, genel API URL'sinde kullanılacaktır.

Dağıtımdan sonra URL aşağıdaki gibi görünecektir. Artık bu bağlantıyı herhangi bir uygulamadan kullanmak mümkündür.

API geçidine erişimi kısıtlamak için bir Authorizer (Yetkilendirici) tanımlamalıyız. Aşağıda gösterildiği gibi bir Cognito Yetkilendiricisi tanımlayabiliriz.
Aşağıdaki görselde görüldüğü gibi, Authorization, yetkili API metodunu kullanmak için isteğin header kısmına eklenmesi gereken JWT belirtecidir (token).
Cognito Hosted UI, bir Cognito kullanıcısı/şifresi ile gönderildiğinde, Cognito kullanıcıyı
id_token ve ek state verilerini aktararak callback URL'sine yönlendirecektir.Header kısmına eklememiz gereken belirtecin Token Source altında "Authorization" olarak adlandırıldığını görün.

Cognito tabanlı Yetkilendirici tanımlandıktan sonra aşağıdaki gibi kullanılabilir:

Öte yandan, API Gateway için Authorizer tanımlamak istemiyorsanız, API URL'sine erişimi aşağıda gösterildiği gibi "Resource Policy" (Kaynak Politikası) ile kısıtlayabilirsiniz.
Eğer Resource Policy değiştirilirse/eklenirse, API tekrar dağıtılmalıdır.
xxx.xxx.xxx.xxx olarak gösterilen IP, sunucunun IP'si olabilir. Birisi URL'ye farklı bir IP'den erişmeye çalıştığında şu mesaj gösterilecektir:{"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"}
Resource Policy JSON kodu aşağıdaki gibi olacaktır:
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. Web Klasörü Olarak Kullanılacak Genel S3 Kovası
Çözüm için iki S3 kovasına (bucket) ihtiyacımız var. İlki önceki bölümlerde oluşturuldu. İkincisi ise şimdi oluşturuluyor ve web klasörü olarak kullanılacak. İlki, tüm dosyaları saklamak için özel kova olarak kullanılmıştı.

Web klasörü olarak genel bir S3 kovası oluşturun. Bu kova bir
callback.html dosyası içerir, böylece Cognito geri çağırma (callback) adresi olarak kullanılabilir.
Web için S3 kovası genel (public) olmalıdır. Bu nedenle aşağıdaki politika uygulanabilir:
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/*"
}
]
}
Kaynak Dosyaları İndirin
Callback.html ve diğer kaynak dosyalarını aşağıdaki bağlantılardan indirebilirsiniz:6. Cognito Kullanıcı Havuzu Oluşturma ve Yapılandırma
- Callback adresi:
https://web-s3-for-interfacing.s3.eu-west-2.amazonaws.com/Callback.html - OAuth 2.0 Flows: "implicit grant" seçeneğini işaretleyin.
- OAuth 2.0 Scopes: email, openid, profile.
Aşağıdaki hosted UI bağlantısını inceleyin.
Hosted Cognito giriş sayfasına parametre göndermek için ek bir "state" URL parametresi ekleyin. "state" parametresi
Callback.html dosyasına iletilecektir.Cognito Hosted UI bağlantısı aşağıda gösterildiği gibi birçok URL parametresi içerir:
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.pdfAlanlar:
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 özel bir URL parametresidir. Hosted UI sayfasına gönderilebilir ve Callback.html sayfasına geri döndürülür.Aşağıda gösterildiği gibi bir client app oluşturulmalıdır:

App client ayarları aşağıda gösterildiği gibi onaylanabilir:

Hosted UI için URL olarak kullanılabilmesi için bir alan adı (domain name) ayarlanmalıdır.

7. Senaryo Nasıl Test Edilir?
Kısıtlı erişime izin veren API'nin Cognito Kullanıcı Havuzu kullanılarak nasıl test edileceğini görelim.
Herhangi bir son kullanıcı bu süreci başlatmak için bir bağlantıya tıklayabilir. Aşağıdaki HTML içeriğini barındıran bir web sayfamız olduğunu varsayalım. Görüldüğü gibi, her dosya için bağlantı, Cognito hosted UI'nın URL'sidir.
LinkToS3Files.html dosyası senaryoyu test etmek için kullanılabilir.Test Dosyalarını İndirin
Sonuç
Umarım bu makale AWS bulut ortamına yeni başlayanlar için faydalı olmuştur.
Bulut Bilişim Hizmetleri
AWS, Azure ve Google Cloud platformlarında altyapı tasarımı, migrasyon, yönetim ve optimizasyon hizmetleri sunuyoruz.
Hizmetimizi İnceleBizimle İletişime Geçin
AWS ve bulut bilişim çözümlerimiz hakkında detaylı bilgi almak için ekibimizle görüşün.
İletişim