Bulut Bilişim
12 dk okuma

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

Cognito kullanıcı havuzları, API Gateway, Lambda ve S3 entegrasyonu ile özel dosyalara güvenli erişim sağlama rehberi.

N
Necmettin Demir
21 Temmuz 2023
Yükleniyor...

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

AWS Cognito S3
AWS Cognito S3

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.
Sistem Mimarisi
Sistem Mimarisi
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.
  1. Özel S3 Kovası (Bucket) Nasıl Oluşturulur?
  2. Özel S3 Kovasındaki Nesnelere Erişim İzni İçin Özel Bir Politika Nasıl Oluşturulur?
  3. Özel S3 Kovasındaki Nesnelere Erişmek İçin Bir Lambda Fonksiyonu Nasıl Oluşturulur?
  4. Lambda Fonksiyonunu Kullanmak İçin Gateway API Nasıl Oluşturulur?
  5. Web Klasörü Olarak Kullanmak İçin Genel S3 Kovası Nasıl Oluşturulur?
  6. Cognito Kullanıcı Havuzu Nasıl Oluşturulur ve Ayarları Nasıl Yapılandırılır?
  7. 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 Bucket Oluşturma
S3 Bucket Oluşturma
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.
Dosya Yükleme
Dosya Yükleme

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 GetObject işlemini gerçekleştirmek üzere yeni bir tane oluşturun:
Policy Listesi
Policy Listesi
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:
Policy Ayarları 1
Policy Ayarları 1
Kaynak (resource) olarak "specific" seçin ve politikanın istediğiniz yeteneklere sahip olması için özel S3 kovanızı belirleyin:
Policy Ayarları 2
Policy Ayarları 2
Politikanıza bir ad verin ve oluşturun. Herhangi bir isim verebilirsiniz ancak bunu hatırlamanız gerekecektir.
Policy Ayarları 3
Policy Ayarları 3
Ö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:
Policy JSON
Policy JSON
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 Oluşturma
Lambda Oluşturma
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.
Lambda Kodu Değiştirme
Lambda Kodu Değiştirme
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:
Lambda Rolünü Bulma
Lambda Rolünü Bulma
Ö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.
Politika Ekleme
Politika Ekleme
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.
REST API Oluşturma
REST API Oluşturma
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:
Resource Oluşturma Adım 1
Resource Oluşturma Adım 1
Burada oluşturulan kaynak (resource), daha sonra API'nin URL'sinde kullanılacaktır.
Resource Oluşturma Adım 2
Resource Oluşturma Adım 2
Oluşturduğunuz kaynak için GET metodu oluşturun:
GET Method Oluşturma
GET Method Oluşturma
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.
Lambda Proxy Entegrasyonu
Lambda Proxy Entegrasyonu
GET metodu oluşturulduktan sonra, API Gateway Metodu ve Lambda fonksiyonu arasındaki akış aşağıdaki gibi görünecektir:
Akış Görünümü
Akış Görünümü
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.
CORS Etkinleştirme
CORS Etkinleştirme
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.
API Dağıtımı
API Dağıtımı
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.
Dağıtım URL'si
Dağıtım URL'si
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 Authorizer Tanımlama
Cognito Authorizer Tanımlama
Cognito tabanlı Yetkilendirici tanımlandıktan sonra aşağıdaki gibi kullanılabilir:
Yetkilendirici Kullanımı
Yetkilendirici Kullanımı
Ö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 Ayarı
Resource Policy Ayarı
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ı.
İki S3 Kovası Yapısı
İki S3 Kovası Yapısı
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 Bucket Oluşturma
Web Bucket Oluşturma
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.pdf
Alanlar:
  • 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 ö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:
Client App Oluşturma
Client App Oluşturma
App client ayarları aşağıda gösterildiği gibi onaylanabilir:
App Client Ayarları
App Client Ayarları
Hosted UI için URL olarak kullanılabilmesi için bir alan adı (domain name) ayarlanmalıdır.
Domain Ayarı
Domain Ayarı

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 İncele

Bizimle İletişime Geçin

AWS ve bulut bilişim çözümlerimiz hakkında detaylı bilgi almak için ekibimizle görüşün.

İletişim