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

Сценарий
Предположим, что вы разрабатываете некоторые приложения для своего клиента. Однако есть некоторые файлы, связанные с записями в приложениях, такие как 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.
- Как создать приватный S3-бакет?
- Как создать пользовательскую политику для доступа к объектам в приватном S3-бакете?
- Как создать Lambda-функцию для доступа к объектам в приватном S3-бакете?
- Как создать Gateway API для использования Lambda-функции?
- Как создать публичный S3-бакет для использования в качестве веб-папки?
- Как создать и настроить пул пользователей Cognito?
- Как протестировать сценарий?
1. Как создать приватный S3-бакет?
S3 — это один из региональных (region-based) сервисов в AWS. Элементы в S3-бакетах называются объектами (object). Поэтому термины объект и файл взаимозаменяемы для S3-бакетов в AWS.
Оставьте флажок "Block All Public Access" (Блокировать весь публичный доступ) отмеченным. Здесь создаётся приватный 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):
Выберите "specific" в качестве ресурса (resource) и укажите ваш приватный S3-бакет, чтобы политика имела желаемые возможности:

Дайте имя вашей политике и создайте её. Вы можете дать любое имя, но вам нужно будет его запомнить.

Сводка вашей пользовательской политики будет выглядеть следующим образом. Также возможно создать политику, напрямую используя этот 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-функции показывается пример кода "hello". Вместо этого нам нужно разработать собственный код.
Как видно, среда разработки Lambda похожа на лёгкую веб-IDE.

Замените существующий код предоставленным коротким примером кода. Новый код будет выглядеть следующим образом. После изменения кода нажмите кнопку "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-функция будет иметь ограниченный доступ
GetObject к вашему S3-бакету.
Это всё, что нужно сделать для доступа Lambda к вашему S3-бакету. Теперь пришло время создать метод AWS Gateway для использования нашей Lambda-функции.
4. Создание Gateway API для использования Lambda-функции
Создайте AWS Gateway REST API, как показано ниже. Видно, что есть много опций, но мы создаём "REST" API как "New API". Дайте имя вашему API Gateway.

Для создания и запуска AWS GW API есть несколько шагов:
- Создать API
- Создать Resource
- Создать Method
- Развернуть (Deploy) API
Создайте
Resource для вашего REST API, как показано ниже:
Созданный здесь ресурс (resource) будет использоваться позже в URL API.

Создайте метод
GET для созданного ресурса:
Здесь можно создать любой HTTP-метод, такой как
GET, POST, PUT, DELETE и т.д. Для наших нужд мы создаём только GET. Не забудьте связать Lambda-функцию, созданную на предыдущих шагах, с этим методом.Lambda Proxy Integration здесь отмечена. Этот подход позволяет нам обрабатывать всё содержимое, связанное с ответом, в Lambda-функции.

После создания метода
GET поток между методом API Gateway и Lambda-функцией будет выглядеть следующим образом:
Включите CORS для Gateway API, как показано ниже. Опции Default 4xx и Default 5xx могут быть отмечены, чтобы даже ошибки могли возвращаться без проблем.

После создания и настройки всего, связанного с методом AWS Gateway, пришло время развернуть (deploy) API. API развёртывается на стадии (stage), как показано. Также это имя стадии будет использоваться в общем URL API.

После развёртывания URL будет выглядеть следующим образом. Теперь эту ссылку можно использовать из любого приложения.

Для ограничения доступа к API gateway нам нужно определить Authorizer (Авторизатор). Мы можем определить Cognito Authorizer, как показано ниже.
Как видно на следующем изображении, Authorization — это JWT-токен, который должен быть добавлен в заголовок запроса для использования авторизованного метода API.
Когда Cognito Hosted UI отправляется с пользователем/паролем Cognito, Cognito перенаправит пользователя на callback URL, передавая
id_token и дополнительные данные state.Обратите внимание, что токен, который нам нужно добавить в заголовок, назван "Authorization" в разделе Token Source.

После определения 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"}
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-бакет как веб-папку. Этот бакет содержит файл
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=7uuggclp7269oguth08mi2ee04response_type=tokenscope=openid+profile+emailredirect_uri=https://web-s3-for-interfacing.s3.eu-west-2.amazonaws.com/Callback.htmlstate=fn=testFile.pdf
state — это пользовательский URL-параметр. Его можно отправить на страницу Hosted UI и получить обратно на странице Callback.html.Необходимо создать клиентское приложение, как показано ниже:

Настройки App client могут быть подтверждены, как показано ниже:

Необходимо установить доменное имя (domain name) для использования в качестве URL для Hosted UI.

7. Как протестировать сценарий?
Давайте посмотрим, как тестировать API с ограниченным доступом с использованием пула пользователей Cognito.
Любой конечный пользователь может нажать на ссылку, чтобы начать этот процесс. Предположим, у нас есть веб-страница, содержащая следующий HTML-контент. Как видно, ссылка для каждого файла — это URL hosted UI Cognito.
Файл
LinkToS3Files.html можно использовать для тестирования сценария.Скачать тестовые файлы
Заключение
Надеюсь, эта статья была полезна для начинающих работать с облачной средой AWS.
Услуги облачных вычислений
Мы предоставляем услуги по проектированию инфраструктуры, миграции, управлению и оптимизации на платформах AWS, Azure и Google Cloud.
Посмотреть наши услугиСвяжитесь с нами
Свяжитесь с нашей командой для получения подробной информации о наших решениях AWS и облачных вычислений.
Контакты