Сегодня мы раскроем секретные ингредиенты, которые делают наши технологии уникальными. Наш шеф-СТО Евгений Хацко расскажет о том, как приготовить Django Nginx Secure Link. Приятного прочтения.
Мы сделали первый релиз нашего пакета, его можно встретить вот здесь. Мотивацией послужило наличие проблемы со скрытием файлов от публичного доступа для неавторизованных пользователей или, если идти глубже, и вовсе скрыть файл ото всех, создавая временные ссылки под каждый отдельный файл.
Возможно, что кто-то увидит в этом отклики объектного хранилища и скажет «есть же AWS S3, Azure Blobs, etc», но здесь существуют некоторые проблемы и сложности, которые препятствуют быстрой интеграции такого рода хранилищ. Не всегда видится целесообразным подключать в работающий проект объектное хранилище, вот несколько таких примеров из нашего опыта:
Это лишь малый список примеров, в которых намного проще взять Nginx-модуль, закрыв публичный доступ к файлам, добавив компонент генерации приватных ссылок для выдачи файлов наружу, тем самым не меняя всю логику работы кодовой базы с файлами. Наш пакет позволяет на простых и не только проектах, где Nginx выступает frontend-сервером делегировать защиту файлов непосредственно на веб-сервер, разгружая наш backend.
Решение подходит и для достаточно сложных по архитектуре решений, где файлы могут лежать на отдельных серверах, доступ к которым будет осуществлен через Nginx.
Наш пакет позволяет брать на себя генерацию временных ссылок на файлы, ограничивая публичный доступ, а сам Nginx уже проверяет эти ссылки и принимает решение об отдаче файла. У Nginx есть отличный модуль ngx_http_secure_link_module для такой задачи, мы же взяли его за основу и написали прослойку между Django и данным модулем. Решение получилось простым и удобным, мы также учли специфичные случаи, когда требуется скрыть только конкретные директории из хранилища media или же от обратного — сделать только их публичными, все это конфигурируется на уровне пакета и должно быть настроено на уровне Nginx с указанием конкретных locations для media/...
Пакет также содержит вспомогательные manage.py команды, нацеленные на помощь в конфигурации нужного блока для location.
Работа еще кипит и будут вноситься улучшения и правки. Есть несколько открытых задач, которые в скором времени будут включены в очередной релиз. Если есть интерес к данному пакеты и желание его улучшить , то пишите и участвуйте. Будем рады любой вовлеченности, даже похейтить можно с целью улучшения пакеты.
Давайте немного погрузимся в техническую часть и рассмотрим пример настройки Django-проекта и конфиграции для виртуального хоста Nginx.
pip install django-nginx-secure-links
В зависимости от версии Django необходимо указать следующие настройки:
Общие настройки для всех версий Django
INSTALLED_APPS = (
...,
'nginx_secure_links',
)
SECURE_LINK_EXPIRATION_SECONDS = 100
SECURE_LINK_SECRET_KEY = '8SypVsPwf3PypUfdVmos9NdmQNCsMG'
SECURE_LINK_TOKEN_FIELD = 'token'
SECURE_LINK_EXPIRES_FIELD = 'exp'
SECURE_LINK_PRIVATE_PREFIXES = [
'private',
]
SECURE_LINK_PUBLIC_PREFIXES = []
DEFAULT_FILE_STORAGE = 'nginx_secure_links.storages.FileStorage'
STORAGES = {
"default": {
"BACKEND": "nginx_secure_links.storages.FileStorage",
},
"staticfiles": {
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
},
}
Также в качестве примера укажем настройку MEDIA_URL и MEDIA_ROOT:
MEDIA_URL = '/media/'
MEDIA_ROOT = '/var/www/html/media/'
Мы добавили вспомогательную команду secure_links_nginx_location
для manage.py
, которую удобно использовать для автоматической генерации блока необходимых location
, учитывая текущие настройки Django-проекта:
python manage.py secure_links_nginx_location
В консоль будет выведен пример блока конфигурации, который и нужно вставить в файл site.conf:
location /media/private/ {
secure_link $arg_token,$arg_exp;
secure_link_md5 "$secure_link_expires$uri 8SypVsPwf3PypUfdVmos9NdmQNCsMG";
if ($secure_link = "") {
return 403;
}
if ($secure_link = "0") {
return 410;
}
alias /var/www/html/media/private/;
}
location /media/ {
alias /var/www/html/media/;
}
1. Рассмотрим пример модели данных, которая сохраняет файлы в приватную директорию, указанную в настройке SECURE_LINK_PRIVATE_PREFIXES
:
from django.db import models
class PrivateDocument(models.Model):
file = models.FileField(upload_to='private/documents')
2. Создадим новую запись в БД, загрузив файл через административную панель для PrivateDocument
.
3. Чтобы сгенерировать защищенную ссылку, достаточно обратиться к свойству url
у поля file
-> obj.file.url
:
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from models import PrivateDocument
def private_document_view(request, pk):
obj = get_object_or_404(PrivateDocument, pk=pk)
private_url = obj.file.url
return HttpResponse(private_url)
Переменная private_url
будет хранить приватную ссылку, содержащую два дополнительных GET-параметра:
Пример ссылки: /media/private/documents/file1.txt?token=S9tBKKkXcAq77g8MrVe6LQ&exp=1721519555
GET-параметр token
(см. настройку SECURE_LINK_TOKEN_FIELD
)
exp
(см. настройку SECURE_LINK_EXPIRES_FIELD
)Стоить учесть, что если файлы не находятся в приватных директория, перечисленных в настройке SECURE_LINK_PRIVATE_PREFIXES
, то генерация приватной ссылки будет пропущена. В результате мы получим публичную ссылку без дополнительных GET-параметров token
и exp
.
Полный рабочий пример под разные версии Django можно найти по этой ссылке. В примере приводится простейшая установка Nginx-модуля через пакет nginx-extras
, но стоит отметить, что в случае сборки Nginx из исходного кода можно также добавить данный модуль на шаге ./configure
, избегая установки nginx-extras
в систему. Мы оформили репозиторий с примером, создав ветки под конкретные версии Django-фреймворка, надеемся это упростит интеграцию пакета в ваши проекты.