Nginx: auth_basic и реверс-прокси

Aug. 21, 2020

Что нужно было

Я написал веб-приложение для хостинга картинок. Речь идёт об imgs.

Оно представляет собой простую HTML-форму для отправки файла. После загрузки картинки на странице (всё происходит на главной) появляется прямая ссылка на изображение.

Хостинг для личного использования, поэтому сайт необходимо защитить базовой аутентификацией, чтобы кто угодно не мог загрузить файлы на мой сервер.

Также хочется иметь доступ к файлам из браузера. В приложении такой веб-интерфейс не предусмотрен, поэтому нужен autoindex (пример). Его тоже следует защитить аутентификацией.

Способ размещения:

Особенности работы приложения:

Итого нужно:

  1. Организовать просмотр содержимого uploads/ по адресу: https://example.org/uploads/.

  2. Защитить базовой аутентификацией URL:

    • https://example.org/
    • https://example.org/uploads/
  3. Прямые ссылки на карнтинки должны быть доступны всем.

Реализация

Начало конфига виртуального хоста nginx:

server {
        listen 80;
        listen 443 ssl http2;

        root /var/www/example.org/data/imgs/;

        server_name example.org;

        ssl_certificate /etc/letsencrypt/live/exampl.org/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/example.org/privkey.pem;

        if ($scheme != "https") {
                return 301 https://$host$request_uri;
        }

# СЮДА ВСТАВЛЯЕТСЯ КОД ЛОКЕЙШЕНОВ #

}

Тут задаём базовые директивы: слушаем порты, указываем root-папку для сайта, server_name, подключаем SSL-сертификат и делаем редирект на HTTPS.

Корневой локейшен. Здесь сожержатся правила для обработки запросов к главной странице сайта. Для него включена аутентификация (auth_basic), а сами запросы будут обрабытываться бэкендом (uWSGI):

        location / {
                include uwsgi_params;
                uwsgi_pass unix:/tmp/example.org.sock;
                auth_basic "Authentication required";
                auth_basic_user_file /var/www/example.org/.htpasswd;
        }

Далее локейшен uploads/ для просмотра файлов на сервере. Для него, соответственно, включены autoindex и базовая аутентификация:

        location /uploads {
                autoindex on;
                auth_basic "Authentication required";
                auth_basic_user_file /var/www/example.org/.htpasswd;
        }

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

Создадим новый локейшен с регулярным выраженим для того, чтобы все запросы к картинкам (т.е. к URI, которые содержат .jpg, .png и так далее в конце) не требовали авторизации, отключаем для них auth_basic:

        location ~* \.(jpg|jpeg|png|gif|webp|svg)$ {
                auth_basic off;
                include uwsgi_params;
                uwsgi_pass unix:/tmp/example.org.sock;
        }

Если просто поставить в локейшене auth_basic off;, то картинки будут возвращать 404 ошибку даже если на самом деле существуют. Это произойдёт потому, что как уже было сказано выше, роутинг таких запросов происходит в бэкенде — в приложении. Поэтому в этот локейшен также следует добавить директивы для проксирования (include uwsgi_params и uwsgi_pass).

Теперь мы сталкиваемся с новой проблемой — в автоиндексе по адресу https://example.org/uploads/ нельзя просмотреть картинки, возвращается 404 ошибка.

Проблема заключается в том, что мы ранее для любых картинок (а следовательно и любых URI, которые содержат в конце расширения картинок) в локейшене ~* \.(jpg|jpeg|png|gif|webp|svg)$ сделали проксирование в бэкенд.

Когда мы открываем картинку через автоиндекс, то запрос к картинке имеет вид: https://example.org/uploads/xyz.jpg. nginx отправляет его бэкенду, а поскольку наш бэкенд умеет роутить только запросы вида https://example.org/xyz.jpg, то мы получаем ошибку 404.

Чтобы избежать этого нам нужно добавить ещё один локейшн с регулярным выражением. В нём мы не будем перенаправлять запросы в бэкенд. Картинки будет отдавать сам nginx в обход приложения.

Для всех запросов к URI вида /uploads/xyz.jpg будет работать только одна директива — try_files, которая вернёт нам картинку или 404, если такой нет в папке:

        location ~* \/uploads/(.*)\.(jpg|jpeg|png|gif|webp|svg)$ {
                try_files $uri $uri/ =404;
             }

Этот локейшен следует разместить до локейшена ~* \.(jpg|jpeg|png|gif|webp|svg)$. Это нужно из-за того, что в nginx локейшены с регулярными выражениями имеют равный приоритет выполнения (они приоритетнее обычных локейшенов без регулярок) и выполняются по порядку следования.

Нам необходимо, чтобы локейшен ~* \/uploads/(.*)\.(jpg|jpeg|png|gif|webp|svg)$ выполнялся раньше, иначе он будет перезаписан локейшеном ~* \.(jpg|jpeg|png|gif|webp|svg)$ и мы при запросе к https://example.org/uploads/xyz.jpg всё равно получим ошибку 404.

Задача решена.

^