Skip to main content

Django storage based on Nginx secure links module

Project description

Build Status Coverage

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/'
DEFAULT_FILE_STORAGE = 'nginx_secure_links.storages.FileStorage'
SECURE_LINK_SECRET_KEY = 'KfM6aA6M7H'
  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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django-nginx-secure-links-0.0.6.tar.gz (10.6 kB view details)

Uploaded Source

Built Distribution

django_nginx_secure_links-0.0.6-py3-none-any.whl (10.3 kB view details)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page