Как получить доступ к приватным объектам S3 с помощью AWS Cognito

Руководство по обеспечению безопасного доступа к приватным файлам с использованием пулов пользователей Cognito, API Gateway, Lambda и интеграции S3.

N
Necmettin Demir
21 июля 2023 г.
Загрузка...

Как получить доступ к приватным объектам S3 с помощью AWS Cognito

AWS Cognito S3
AWS Cognito S3

Сценарий

Предположим, что вы разрабатываете некоторые приложения для своего клиента. Однако есть некоторые файлы, связанные с записями в приложениях, такие как PDF, Word, Excel и т.д. Для простоты сценария предположим, что эти файлы хранятся в одном приватном (private) S3-бакете в AWS.
Пользователям необходимо иметь возможность доступа к этим соответствующим файлам из приватного S3-бакета через URL-ссылку в приложениях. Нашему решению необходимо работать как переносимое (portable) решение для любого корпоративного программного обеспечения.

Введение

Цель этой статьи — показать, как загружать файлы из приватного S3-бакета с использованием пулов пользователей Cognito. Помимо Cognito, демонстрируется поток от Cognito к API Gateway с авторизатором (Authorizer) и взаимодействие API Gateway с Lambda.
Для каждого шага из консоли AWS было предоставлено как можно больше скриншотов. Особенно для начинающих добавлено множество визуальных материалов для большей ясности шагов.

Предварительные знания

Для лучшего понимания того, что разрабатывается в этой статье, может быть полезно предварительное чтение. Особенно для новичков в AWS будут полезны следующие ссылки:

Что нужно сделать?

Для такой задачи можно закодировать множество потоков или методов. Здесь мы реализуем метод, показанный ниже. Краткое описание того, как реализовать сценарий, представлено на следующем изображении.
Следующее изображение показывает, что нам необходимо создать некоторые элементы, такие как пул пользователей Cognito, S3-бакеты, методы API Gateway, функции Lambda и т.д. После создания всех сущностей в среде AWS нам необходимо соответствующим образом настроить их для совместной работы.
Архитектура системы
Архитектура системы
Лучше создавать все элементы в среде AWS в обратном порядке. Например, чтобы использовать Lambda с методом API, если сначала разработать функцию Lambda, то при создании метода API Gateway эту функцию можно легко привязать. Аналогично, на Шаге 5 мы должны создать веб-бакет S3 и поместить в него файл callback.html, чтобы использовать его при создании пула пользователей Cognito на Шаге 6. Конечно, это не обязательно, но такой порядок упростит разработку. Поэтому здесь выбран этот подход.

План

Мы будем искать ответы на следующие вопросы. Обратите внимание, что для выполнения всех шагов в этой статье у вас должна быть учётная запись AWS.
  1. Как создать приватный S3-бакет?
  2. Как создать пользовательскую политику для доступа к объектам в приватном S3-бакете?
  3. Как создать Lambda-функцию для доступа к объектам в приватном S3-бакете?
  4. Как создать Gateway API для использования Lambda-функции?
  5. Как создать публичный S3-бакет для использования в качестве веб-папки?
  6. Как создать и настроить пул пользователей Cognito?
  7. Как протестировать сценарий?

1. Как создать приватный S3-бакет?

S3 — это один из региональных (region-based) сервисов в AWS. Элементы в S3-бакетах называются объектами (object). Поэтому термины объект и файл взаимозаменяемы для S3-бакетов в AWS.
Оставьте флажок "Block All Public Access" (Блокировать весь публичный доступ) отмеченным. Здесь создаётся приватный S3-бакет. Хотя существует множество дополнительных параметров конфигурации, для простоты решения мы создаём его с настройками по умолчанию.
Создание S3 бакета
Создание S3 бакета
Загрузите несколько объектов в S3-бакет для тестирования приватного доступа. Затем попробуйте получить доступ к этим объектам через неавторизованных пользователей или возможные ссылки доступа. Хотя мы знаем такие файлы, как PDF, DOC, XLS и т.д., в терминологии AWS S3 все они называются объектами.
Загрузка файлов
Загрузка файлов

2. Создание политики для доступа к объектам в приватном S3-бакете

IAM (Identity and Access Management — Управление идентификацией и доступом) в AWS является основой всех сервисов! Пользователи, Группы, Роли и Политики — это базовые концепции, с которыми мы должны быть знакомы.
Существует множество встроенных (built-in) ролей, и каждая роль имеет множество встроенных политик, означающих разрешения. Они называются "AWS Managed" (Управляемые AWS). Однако также возможно создавать "Customer Managed" (Управляемые клиентом) роли и политики. Поэтому здесь создаётся пользовательская политика.
  • Создайте пользовательскую IAM-политику для получения объектов из вашего приватного S3-бакета.
  • Найдите список существующих политик в AWS и создайте новую для выполнения операции GetObject только из вашего приватного S3-бакета, как показано ниже:
Список политик
Список политик
Создайте пользовательскую политику, как показано ниже. Выберите S3 в качестве сервиса и только GetObject в качестве действия (action):
Настройки политики 1
Настройки политики 1
Выберите "specific" в качестве ресурса (resource) и укажите ваш приватный S3-бакет, чтобы политика имела желаемые возможности:
Настройки политики 2
Настройки политики 2
Дайте имя вашей политике и создайте её. Вы можете дать любое имя, но вам нужно будет его запомнить.
Настройки политики 3
Настройки политики 3
Сводка вашей пользовательской политики будет выглядеть следующим образом. Также возможно создать политику, напрямую используя этот JSON-контент:
Policy JSON
Policy JSON
JSON-определение политики:
JSON
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::private-s3-for-interfacing/*"
        }
    ]
}

3. Создание Lambda-функции для доступа к объектам в приватном S3-бакете

Здесь используется последняя версия NodeJS для Lambda-функции. Создайте Lambda-функцию и выберите NodeJS. Вы можете выбрать любой поддерживаемый язык для Lambda-функции, такой как Python, Go, Java, .NET Core и т.д.
Создание Lambda
Создание Lambda
При создании Lambda-функции показывается пример кода "hello". Вместо этого нам нужно разработать собственный код.
Как видно, среда разработки Lambda похожа на лёгкую веб-IDE.
Изменение кода Lambda
Изменение кода Lambda
Замените существующий код предоставленным коротким примером кода. Новый код будет выглядеть следующим образом. После изменения кода нажмите кнопку "Deploy" для использования Lambda-функции.
Для простоты сценария имя бакета используется статически. Имя файла отправляется как параметр с именем fn. Хотя тип контента (content type) по умолчанию принимается как pdf, это может быть любой тип файла, реализованный в коде Lambda-функции. Поскольку мы предпочтём использовать функцию proxy Lambda-функции в связке API Gateway, заголовок ответа (response header) содержит некоторые дополнительные данные.
Код Lambda на NodeJS (возврат в виде Blob):
JavaScript
// Код Lambda-функции выглядит следующим образом
// Этот код вернёт ответ как blob-контент
// Для загрузки файла можно использовать Callback-to-Download-Blob.html во вложениях

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, // Ключ к успеху
          '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
    }
  }
}
Также возможно использовать код Python в Lambda-функции, как показано ниже:
Python
# Следующий код может быть разработан подобно примеру на NodeJS выше
    
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'
                         # При необходимости здесь можно добавить код, связанный с CORS
                        },
            'body': base64.b64encode(file).decode('utf-8'),           
            'isBase64Encoded': True
        }
    except:
        return {
            'headers': { 'Content-type': 'text/html' },
            'statusCode': 200,
            'body': 'Error occurred in Lambda!' 
        }
Другой метод — использовать presigned URL с Lambda:
JavaScript
// Этот метод предоставит presigned url
// Для использования ссылки presigned URL можно использовать файл 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 // секунды
  });

  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-функции вместе с ней создаётся роль. Однако эта роль не имеет прав доступа к объектам в вашем приватном S3-бакете. Теперь нам нужно прикрепить "Customer Managed" политику, созданную на предыдущих шагах, к этой роли, созданной с Lambda-функцией.
После создания Lambda-функции вы можете найти автоматически созданную роль, как показано ниже:
Поиск роли Lambda
Поиск роли Lambda
Прикрепите пользовательскую политику, созданную на предыдущем шаге, к этой роли; таким образом Lambda-функция будет иметь ограниченный доступ GetObject к вашему S3-бакету.
Прикрепление политики
Прикрепление политики
Это всё, что нужно сделать для доступа Lambda к вашему S3-бакету. Теперь пришло время создать метод AWS Gateway для использования нашей Lambda-функции.

4. Создание Gateway API для использования Lambda-функции

Создайте AWS Gateway REST API, как показано ниже. Видно, что есть много опций, но мы создаём "REST" API как "New API". Дайте имя вашему API Gateway.
Создание REST API
Создание REST API
Для создания и запуска AWS GW API есть несколько шагов:
  • Создать API
  • Создать Resource
  • Создать Method
  • Развернуть (Deploy) API
Создайте Resource для вашего REST API, как показано ниже:
Создание Resource Шаг 1
Создание Resource Шаг 1
Созданный здесь ресурс (resource) будет использоваться позже в URL API.
Создание Resource Шаг 2
Создание Resource Шаг 2
Создайте метод GET для созданного ресурса:
Создание GET Method
Создание GET Method
Здесь можно создать любой HTTP-метод, такой как GET, POST, PUT, DELETE и т.д. Для наших нужд мы создаём только GET. Не забудьте связать Lambda-функцию, созданную на предыдущих шагах, с этим методом.
Lambda Proxy Integration здесь отмечена. Этот подход позволяет нам обрабатывать всё содержимое, связанное с ответом, в Lambda-функции.
Интеграция Lambda Proxy
Интеграция Lambda Proxy
После создания метода GET поток между методом API Gateway и Lambda-функцией будет выглядеть следующим образом:
Просмотр потока
Просмотр потока
Включите CORS для Gateway API, как показано ниже. Опции Default 4xx и Default 5xx могут быть отмечены, чтобы даже ошибки могли возвращаться без проблем.
Включение CORS
Включение CORS
После создания и настройки всего, связанного с методом AWS Gateway, пришло время развернуть (deploy) API. API развёртывается на стадии (stage), как показано. Также это имя стадии будет использоваться в общем URL API.
Развёртывание API
Развёртывание API
После развёртывания URL будет выглядеть следующим образом. Теперь эту ссылку можно использовать из любого приложения.
URL развёртывания
URL развёртывания
Для ограничения доступа к API gateway нам нужно определить Authorizer (Авторизатор). Мы можем определить Cognito Authorizer, как показано ниже.
Как видно на следующем изображении, Authorization — это JWT-токен, который должен быть добавлен в заголовок запроса для использования авторизованного метода API.
Когда Cognito Hosted UI отправляется с пользователем/паролем Cognito, Cognito перенаправит пользователя на callback URL, передавая id_token и дополнительные данные state.
Обратите внимание, что токен, который нам нужно добавить в заголовок, назван "Authorization" в разделе Token Source.
Определение Cognito Authorizer
Определение Cognito Authorizer
После определения Cognito-based Authorizer его можно использовать следующим образом:
Использование авторизатора
Использование авторизатора
С другой стороны, если вы не хотите определять авторизатор для API Gateway, вы можете ограничить доступ к URL API с помощью "Resource Policy" (Политика ресурсов), как показано ниже.
Если Resource Policy изменена/добавлена, API должен быть повторно развёрнут. IP, показанный как xxx.xxx.xxx.xxx, может быть IP сервера. Когда кто-то попытается получить доступ к URL с другого IP, будет показано следующее сообщение:
{"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
Настройка Resource Policy
JSON-код Resource Policy будет следующим:
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. Публичный S3-бакет для использования в качестве веб-папки

Для решения нам нужны два S3-бакета (bucket). Первый был создан в предыдущих разделах. Второй создаётся сейчас и будет использоваться как веб-папка. Первый использовался как приватный бакет для хранения всех файлов.
Структура двух S3 бакетов
Структура двух S3 бакетов
Создайте публичный S3-бакет как веб-папку. Этот бакет содержит файл callback.html, поэтому его можно использовать в качестве адреса обратного вызова (callback) Cognito.
Создание веб-бакета
Создание веб-бакета
S3-бакет для веб должен быть публичным (public). Поэтому можно применить следующую политику:
JSON
// JSON политики будет выглядеть следующим образом

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

Скачать исходные файлы

Вы можете скачать Callback.html и другие исходные файлы по следующим ссылкам:

6. Создание и настройка пула пользователей Cognito

  • Адрес callback: https://web-s3-for-interfacing.s3.eu-west-2.amazonaws.com/Callback.html
  • OAuth 2.0 Flows: отметьте опцию "implicit grant".
  • OAuth 2.0 Scopes: email, openid, profile.
Изучите ссылку hosted UI ниже. Чтобы отправить параметр на hosted страницу входа Cognito, добавьте дополнительный URL-параметр "state". Параметр "state" будет передан в файл Callback.html.
Ссылка Cognito Hosted UI содержит множество URL-параметров, как показано ниже:
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
Поля:
  • 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 — это пользовательский URL-параметр. Его можно отправить на страницу Hosted UI и получить обратно на странице Callback.html.
Необходимо создать клиентское приложение, как показано ниже:
Создание клиентского приложения
Создание клиентского приложения
Настройки App client могут быть подтверждены, как показано ниже:
Настройки App Client
Настройки App Client
Необходимо установить доменное имя (domain name) для использования в качестве URL для Hosted UI.
Настройка домена
Настройка домена

7. Как протестировать сценарий?

Давайте посмотрим, как тестировать API с ограниченным доступом с использованием пула пользователей Cognito.
Любой конечный пользователь может нажать на ссылку, чтобы начать этот процесс. Предположим, у нас есть веб-страница, содержащая следующий HTML-контент. Как видно, ссылка для каждого файла — это URL hosted UI Cognito.
Файл LinkToS3Files.html можно использовать для тестирования сценария.

Скачать тестовые файлы


Заключение

Надеюсь, эта статья была полезна для начинающих работать с облачной средой AWS.

Услуги облачных вычислений

Мы предоставляем услуги по проектированию инфраструктуры, миграции, управлению и оптимизации на платформах AWS, Azure и Google Cloud.

Посмотреть наши услуги

Свяжитесь с нами

Свяжитесь с нашей командой для получения подробной информации о наших решениях AWS и облачных вычислений.

Контакты