company_banner

Проксируем файлы из AWS S3 средствами nginx

    Казалось бы, задача реализации фронтенда для AWS на nginx звучит как типовой кейс для StackOverflow — ведь проблем с проксированием файлов из S3 быть не может? На деле выяснилось, что готовое решение не так-то просто найти, и данная статья должна исправить эту ситуацию.



    Зачем это вообще может понадобиться?


    1. Контроль доступа к файлам средствами nginx — актуально для концепции IaC (инфраструктура как код). Все изменения, связанные с доступом, будут вноситься только в конфигах, которые лежат в проекте.
    2. Если отдавать файлы через свой nginx, появляется возможность их кэшировать и сэкономить тем самым на запросах к S3.
    3. Подобный прокси поможет абстрагироваться от типа хранилища файлов для разных инсталляций приложения (ведь помимо S3 существуют и другие решения).

    Сформулируем рамки


    • Исходный bucket должен быть приватным — нельзя разрешать анонимным пользователям качать файлы напрямую из S3. Если в вашем случае это ограничение не работает, то просто используйте proxy_pass и дальше можете не читать.
    • Настройка со стороны AWS должна быть одноразовой по принципу «настроил и забыл», дабы упростить эксплуатацию.

    Ищем решение в лоб


    Если ваш оригинальный bucket публичный, то никакие сложности вам не грозят, проксируйте запросы на S3 и всё будет работать. Если же он приватный, то придётся как-то аутентифицироваться в S3. Что нам предлагают коллеги из интернета:

    1. Есть примеры реализации протокола аутентификации средствами nginx. Решение хорошее, но к сожалению, оно рассчитано на устаревший протокол аутентификации (Signature v2), который не работает в некоторых ЦОД Amazon. Если вы попытаетесь воспользоваться этим решением, например, во Франкфурте, то получите ошибку «The authorization mechanism you have provided is not supported. Please use AWS4-HMAC-SHA256». Более свежая версия протокола (Signature v4) гораздо сложнее в реализации, а готовых решений для nginx с ней нет.
    2. Есть сторонний модуль для nginx — ngx_aws_auth. Если судить по исходникам, он поддерживает Signature v4. Однако проект выглядит заброшенным: более года отсутствуют изменения в кодовой базе, а также имеется проблема совместимости с другими модулями, на которую не реагирует разработчик. К тому же, добавление дополнительных модулей в nginx — это само по себе зачастую болезненный шаг.
    3. Можно воспользоваться отдельным 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.


    Читайте также в нашем блоге:

    Флант
    DevOps-as-a-Service, Kubernetes, обслуживание 24×7

    Комментарии 15

      0
      Есть сторонний модуль для nginx — ngx_aws_auth. Если судить по исходникам, он поддерживает Signature v4. Однако проект выглядит заброшенным: более года отсутствуют изменения в кодовой базе, а также имеется проблема совместимости с другими модулями, на которую не реагирует разработчик. К тому же, добавление дополнительных модулей в nginx — это само по себе зачастую болезненный шаг.


      Готовая сборка nginx + ngx_aws_auth. 2 года полёт нормальный.
        –1

        А можно вместо nginx поставить Cloudfront/Cloudflare/Akamai/etc и не иметь проблем

          0
          Надо хостнейм реврайтить, в CloudFlare это можно сделать воркерами.
            +2
            Совершенно верно, если ваш бакет публичный, то можно не заморачиваться с аутентификацией и использовать любой CDN.
            Если же перед вашим приложением стоит задача разграничить права доступа к файлам, например, «разрешить скачивать файлы только из локалки» или «разрешить Пете скачивать документ Х, а остальным запретить», то с кастомным прокси это сделать проще всего.
            Также, помимо проксирования, nginx позволяет обрабатывать файлы на лету, например, ресайзить картинки или накладывать вотермарки. Соответственно, если вы не хотите, чтобы оригиналы картинок ушли в сеть, то решение из статьи вам поможет.
              –3

              Если мне надо будет делать то что вы описали то я возьму готовое решение и не буду заморачиваться и буду платить намного меньше чем вы

                0
                Вы заблуждаетесь насчет платить меньше
                  0

                  И что даже можете рассчет привести? :)


                  Начнем с того что как минимум трафик исходящий с CloudFront дешевле чем с S3 или EC2

            0
            Какую практическую задачу решает Ваш топик?
              +2
              Понимая, что прикладная ценность задачи из сабжа может быть неочевидной, я начал статью с параграфа «зачем это может понадобиться?». Приведу пару кейсов:
              • Дать доступ на скачивание документа Х только Пете, остальным запретить. При этом контроль доступа лежит в недрах приложения, а не в конфигах AWS (работает принцип IaC).
              • У вас сайт, которому требуется ресайзить картинки и клеить вотермерки на лету, а оригиналы картинок лежат в закрытом S3 (вы не хотите, чтобы оригиналы достались кому-то на стороне). При этом вы не хотите получать большие счета за реквесты к S3 и кешируете картинки у себя.

                0
                1. signed URLs
                2. Lambda

                Принцип называется IaaC и заключается он в том что вы описываете все ресурсы в коде (бакет, его конфиг и все остальное) но никак не логика приложения

                  0

                  Поддерживаю. Учитывая ещё и локальность лямбды по отношению к бакету, то это вообще отличное решение. Nginx же годится как общий случай, когда не обязательно есть Амазон, а возможно существует какой-либо s3 в локалке (минио?)

                    0
                    но это же денег будет стоить, нет?
                    Я не разбирался подробно, но задачи, описанные комментом выше, на одном пет-прожекте у меня решаются походом, параллельно с другими нагрузками на минимальном дроплете DO. То есть, для меня это выходит бесплатно, плачу только за (изрядный) объем хранения на AWS. Если заюзать лямбды и амазоновский фронт — то придется за это платить, и платить совсем немало.

                    Это уж я не говорю про то, что обработку у меня делает свободный серверок в докере, который работает as is, без допила, а как это все конвертировать в лямбды — отдельный вопрос и отдельное время.
                      0

                      Я вот заметил, что те кто работает в энтерпрайз автоматически считают, что "облака" — это дёшево, просто действительно для их клиентов это не значительная разница и они ее просто не считают.


                      Соответственно и на пет-проектах никакой нагрузки нет и тоже вроде как дёшево.


                      Но если считать деньги, то очень мало пока кейсов, где в "облаках" будет хоть какой-то смысл.


                      Так что, решение с локальным хранением при кешировании — идея правильная, хотя, конечно, Amazon не оценит.

                        0

                        Все зависит от нагрузки и кол-ва клиентов.
                        Если у вас 3 калеки в год, то конечно DO рулит на минимальном тарифе
                        Хотя вот минимальный дроплет на DO стоит $5/мес, t3.micro — $7.5/мес
                        трафик с Cloudfront дешевле чем с S3
                        Lambda ничего не стоит если нет запросов


                        С учетом того что трафик стоит $0.085/gb чтобы окупить просто стоимость даже дроплета вам надо чтобы у вас было минимум 60gb в месяц
                        А если перед этим поставить Cloudflare то вообще только за первый запрос заплатите


                        Нет разницы работаете вы в ентерпрайзе или в конторе "рога и копыта" чтобы научиться правильно подбирать инструмент и уметь произвести холодный рассчет

                0
                А еще есть 'static web site hosting' у S3 и обычный proxy_pass в любом nginx. Я вообще Varnish для этого использую.

                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                Самое читаемое