Казалось бы, задача реализации фронтенда для AWS на nginx звучит как типовой кейс для StackOverflow — ведь проблем с проксированием файлов из S3 быть не может? На деле выяснилось, что готовое решение не так-то просто найти, и данная статья должна исправить эту ситуацию.
Зачем это вообще может понадобиться?
- Контроль доступа к файлам средствами nginx — актуально для концепции IaC (инфраструктура как код). Все изменения, связанные с доступом, будут вноситься только в конфигах, которые лежат в проекте.
- Если отдавать файлы через свой nginx, появляется возможность их кэшировать и сэкономить тем самым на запросах к S3.
- Подобный прокси поможет абстрагироваться от типа хранилища файлов для разных инсталляций приложения (ведь помимо S3 существуют и другие решения).
Сформулируем рамки
- Исходный bucket должен быть приватным — нельзя разрешать анонимным пользователям качать файлы напрямую из S3. Если в вашем случае это ограничение не работает, то просто используйте
proxy_pass
и дальше можете не читать. - Настройка со стороны AWS должна быть одноразовой по принципу «настроил и забыл», дабы упростить эксплуатацию.
Ищем решение в лоб
Если ваш оригинальный bucket публичный, то никакие сложности вам не грозят, проксируйте запросы на S3 и всё будет работать. Если же он приватный, то придётся как-то аутентифицироваться в S3. Что нам предлагают коллеги из интернета:
- Есть примеры реализации протокола аутентификации средствами nginx. Решение хорошее, но к сожалению, оно рассчитано на устаревший протокол аутентификации (Signature v2), который не работает в некоторых ЦОД Amazon. Если вы попытаетесь воспользоваться этим решением, например, во Франкфурте, то получите ошибку «The authorization mechanism you have provided is not supported. Please use AWS4-HMAC-SHA256». Более свежая версия протокола (Signature v4) гораздо сложнее в реализации, а готовых решений для nginx с ней нет.
- Есть сторонний модуль для nginx — ngx_aws_auth. Если судить по исходникам, он поддерживает Signature v4. Однако проект выглядит заброшенным: более года отсутствуют изменения в кодовой базе, а также имеется проблема совместимости с другими модулями, на которую не реагирует разработчик. К тому же, добавление дополнительных модулей в nginx — это само по себе зачастую болезненный шаг.
- Можно воспользоваться отдельным s3-прокси, коих написано достаточно много. Лично мне понравилось решение на Go — aws-s3-proxy: у него есть готовый и достаточно популярный образ на DockerHub. Но в этом случае приложение обрастёт ещё одним компонентом со своими потенциальными проблемами.
Применяем AWS Bucket Policy
AWS, как правило, пугает новых пользователей своей сложностью и объёмом документации. Но если разобраться, то понимаешь, что он устроен очень логично и гибко. В Amazon'е нашлось решение и для нашей задачи — S3 Bucket Policy. Этот механизм позволяет строить гибкие правила авторизации для bucket'а на основе разных параметров клиента или запроса.
Интерфейс генератора политик — AWS Policy Generator
Вот некоторые интересные параметры, к которым можно привязаться:
- IP (
aws:SourceIp
), - заголовок Referer (
aws:Referer
), - заголовок User-Agent (
aws:UserAgent
), - остальные — описаны в документации.
Привязка к IP — хороший вариант только при условии, что у приложения есть определённое место жительства, а в наше время это редкость. Соответственно, нужно привязываться к чему-то ещё. В качестве решения я предлагаю сгенерировать секретный User-Agent или Referer и отдавать файлы только тем пользователям, которые знают секретный заголовок. Вот как выглядит подобная политика:
{
"Version": "2012-10-17",
"Id": "http custom auth secret",
"Statement": [
{
"Sid": "Allow requests with my secret.",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::example-bucket-for-habr/*",
"Condition": {
"StringLike": {
"aws:UserAgent": [
"xxxyyyzzz"
]
}
}
}
]
}
Немного пояснений:
-
"Version": "2012-10-17"
— это внутренняя кухня AWS, которую править не надо; -
Principal
— кого касается данное правило. Можно указать, что оно работает только для определённой AWS-учётки, но в нашем случае стоит"*"
— это означает, что правило работает вообще для всех, в том числе и анонимных пользователей; -
Resource
— ARN (Amazon Resource Name) bucket'а и шаблон для файлов внутри bucket'а. В нашем случае политика касается всех файлов, которые лежат в bucket'еexample-bucket-for-habr
; -
Condition
— здесь указываются условия, которые должны сойтись, чтобы политика заработала. В нашем случае мы сравниваем предустановленный заголовок User-Agent со строкойxxxyyyzzz
.
А вот как выглядит работа этого правила с точки зрения анонимного пользователя:
$ curl -I https://s3.eu-central-1.amazonaws.com/example-bucket-for-habr/hello.txt
HTTP/1.1 403 Forbidden
$ curl -I https://s3.eu-central-1.amazonaws.com/example-bucket-for-habr/hello.txt -H 'User-Agent: xxxyyyzzz'
HTTP/1.1 200 OK
Осталось настроить nginx для проксирования:
location /s3-media/ {
limit_except GET {
deny all;
}
set $aws_bucket "example-bucket-for-habr";
set $aws_endpoint "s3.eu-central-1.amazonaws.com:443";
set $aws_custom_secret "xxxyyyzzz";
proxy_set_header User-Agent $aws_custom_secret;
rewrite ^/s3-media/(.*)$ /$aws_bucket/$1 break;
proxy_buffering off;
proxy_pass https://$aws_endpoint;
}
Заключение
Итого, единожды написав простую политику для bucket'а, мы получили возможность безопасно проксировать файлы с помощью nginx. При этом мы не привязаны по IP и не зависим от дополнительного ПО.
P.S.
Читайте также в нашем блоге:
- «Awless — мощная альтернативная CLI-утилита для работы с сервисами AWS»;
- «Rook — «самообслуживаемое» хранилище данных для Kubernetes»;
- «Теория и практика бэкапов с Borg».
Автор: Андрей Половов