Skip to content

This repository serves as a module for creating private URLs in the Django Framework using the Nginx module ngx_http_secure_link_module.

License

Notifications You must be signed in to change notification settings

LighTechLLC/django-nginx-secure-links

Repository files navigation

Build Status

Coverage

Django Nginx Secure Links

This module is a Django extension for using ngx_http_secure_link_module. It provides private urls with expiration lifetime by implementing described logic of ngx_http_secure_link_module. The major advantage of the extension is that Django delegates file serving on Nginx layer and does only pre-signed urls generation.

Requirements

Django Nginx Secure Links requires Django 3.2 or later.

Installation

Installing from PyPI is as easy as doing:

pip install django-nginx-secure-links

If you want to install it from source, grab the git repository from GitHub and run setup.py:

git clone git://github.com/lighTechLLC/django-nginx-secure-links.git
cd django-nginx-secure-links
python setup.py install

Nginx module set up

Option 1

Install using apt (Ubuntu example):

sudo apt install nginx-extras

Option 2

Build from sources:

./configure .... --with-http_secure_link_module

Quick example

  1. Django settings set up settings.py:
INSTALLED_APPS = (
    ...
    'nginx_secure_links',
    ...
)
MEDIA_ROOT = '/var/www/media/'
MEDIA_URL = '/media/'
SECURE_LINK_SECRET_KEY = 'KfM6aA6M7H'
  • Django < 4.2
DEFAULT_FILE_STORAGE = 'nginx_secure_links.storages.FileStorage'
  • Django >= 4.2
STORAGES = {
    "default": {
        "BACKEND": "nginx_secure_links.storages.FileStorage",
    },
    "staticfiles": {
        "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
    },
}
  1. Create a private file inside your settings.MEDIA_ROOT:
echo "I'm private text file" > /var/www/media/sample.txt
  1. Let's start runserver and access the file outside of Django file storage. It works and the file is available. There is no access denied, because of runserver mode:
curl http://127.0.0.1:8000/media/sample.txt
  1. Set up Nginx virtual host file site.conf:
server 127.0.0.1;
listen 80;

...

location /media/ {
    secure_link $arg_token,$arg_expires;
    secure_link_md5 "$secure_link_expires$uri KfM6aA6M7H";

    if ($secure_link = "") {
        return 403;
    }

    if ($secure_link = "0") {
        return 410;
    }

    alias /var/www/media/;
}

...
  1. Let's access the file through Nginx host/port.
curl http://127.0.0.1/media/sample.txt

Because of Nginx secure link module protection, the file won't be served without ?token=...&expires=... parameters. Only django users will be able to access files which urls generated by django storage.

Usage

models.py

class Report(models.Model):
    pdf_file = models.FileField(upload_to='reports')

views.py

def report_details(request, report_id)
    instance = Report.objects.get(id=report_id)
    return JsonResponse({'url': instance.pdf_file.url})

json response

{
  "url": "/media/reports/29974.pdf?expires=1599214310&token=ErLcMm96-4h2qsuj2Avo-w"
}

That's it, all uploaded media files through Django will be pre-signed. If you work locally and do not want to install Nginx, let's skip it for local development- django will generate pre-signed urls, but all files will be available because of runserver command serves files and does not provide 3rd-party nginx-secure-link module functionality.

Settings

  • SECURE_LINK_SECRET_KEY

Your specific secret string which Nginx is going to use in secure_link_md5 directive.

  • SECURE_LINK_TOKEN_FIELD (optional, default: token)

Your custom name of the hash GET-parameter (?token=xyz)

  • SECURE_LINK_EXPIRES_FIELD (optional, default: expires)

Your custom name of expiration timestamp GET-parameter (?expires=1599215210)

  • SECURE_LINK_EXPIRATION_SECONDS (optional, default: 86400- 1 day)

Your custom value of expiration seconds. Any pre-signed link will be expired after SECURE_LINK_EXPIRATION_SECONDS.

  • SECURE_LINK_PRIVATE_PREFIXES (optional, default: [])

List of private paths without MEDIA_URL prefix. Just leave it empty for making all media urls private. Example:

MEDIA_URL = '/media/'
SECURE_LINK_PRIVATE_PREFIXES = [
    'documents/',
    'reports/',
]

In such case all /media/documents/ and /media/reports/ urls will be private and pre-signed by using token and expiration time. If any of existing prefixes on the project are not listed in SECURE_LINK_PRIVATE_PREFIXES, so the url will be public.

  • SECURE_LINK_PUBLIC_PREFIXES (optional, default: [])

List of private paths without MEDIA_URL prefix. Example:

MEDIA_URL = '/media/'
SECURE_LINK_PUBLIC_PREFIXES = [
    'avatars/',
    'shared/',
]

In such case only /media/avatars/ and /media/shared/ urls will be public and generated without pre-signed urls. All other urls, will be private and pre-signed by using token and expiration time.

Important If you want to keep all media files privately, SECURE_LINK_PRIVATE_PREFIXES and SECURE_LINK_PUBLIC_PREFIXES should be [].

Custom storage for non-media files

Example 1: We are going to use our own server directory and url prefix instead of settings.MEDIA_ROOT / settings.MEDIA_URL. The example is going to use all default settings.SECURE_LINK_*

from nginx_secure_links.storages import FileStorage

storage = FileStorage(location='/var/www/personal_data/', base_url='/personal/')
storage.url('profile.pdf')

Example 2: We are going to use custom storage with all overridden settings.

from nginx_secure_links.storages import FileStorage

storage = FileStorage(
      location='/var/www/personal_data/',
      base_url='/personal/'
      nginx_secret_key='91rdywY7d4494X',
      expires_field_name='expires_timestamp',
      token_field_name='hash',
      private_prefixes=[],
      public_prefixes=[],
      expires_seconds=60 * 60,  # 60min
  ) # all private
  storage.url('profile.pdf')  # /personal/profile.pdf?hash=mlkiuhbhu83d&expires_timestamp=2147483647

Using It

Generate pre-signed url by passing public url:

python manage.py secure_links_gen_signed /media/reports/sample.pdf

Generates a sample of Nginx location basing on the settings:

python manage.py secure_links_nginx_location

Found a Bug?

Issues are tracked via GitHub issues at the project issue page.

About

This repository serves as a module for creating private URLs in the Django Framework using the Nginx module ngx_http_secure_link_module.

Topics

Resources

License

Stars

Watchers

Forks

Languages