Reputation: 9974
I am building a system that allows users to generate a documents and then download them. The documents are PDFs (not that it matters for the sake of this question) and when they are generated I store them on my local file system that the web server is running on with uuid file names
c7d43358-7532-4812-b828-b10b26694f0f.pdf
but I know "security through obscurity" is not the right solution ...
I want to restrict access to they files on a per account basis if possible. One thing I think I could do is upload them to S3 and provide a signed URL, but I want to avoid that for now if possible.
I am using Nginx/Django/Gunicorn/EC2/S3
What are some other solutions?
Upvotes: 9
Views: 7207
Reputation: 3461
To manage user access to static files in a Django application when serving the files with Nginx, you can implement a secure system to control access. Here’s a step-by-step approach to solve this problem:
First, configure Nginx to restrict direct access to the static files. This can be done by using internal redirects and access control in Nginx.
Edit your Nginx configuration file (e.g., /etc/nginx/sites-available/your_site):
nginx
server {
listen 80;
server_name your_domain.com;
location /static/ {
internal;
alias /path/to/staticfiles/;
}
location /media/ {
internal;
alias /path/to/mediafiles/;
}
location /protected_static/ {
alias /path/to/staticfiles/;
add_header X-Accel-Redirect /static/;
}
location /protected_media/ {
alias /path/to/mediafiles/;
add_header X-Accel-Redirect /media/;
}
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
In this configuration:
/static/ and /media/ locations are marked as internal, meaning they cannot be accessed directly from the outside. The /protected_static/ and /protected_media/ locations act as a proxy to these internal locations, adding an X-Accel-Redirect header to perform an internal redirect.
In Django, create a view that checks user permissions before serving the static files. This view will generate URLs that Nginx can handle.
from django.http import HttpResponse, Http404
from django.conf import settings
import os
def serve_protected_file(request, file_path):
# Check user permissions here
if not request.user.is_authenticated:
raise Http404
# Generate the X-Accel-Redirect header
response = HttpResponse()
response['Content-Disposition'] = 'attachment; filename="%s"' % os.path.basename(file_path)
response['X-Accel-Redirect'] = os.path.join('/protected_static/', file_path)
response['Content-Type'] = ''
return response
Update your urls.py to point to the new view:
from django.urls import path
from . import views
urlpatterns = [
path('protected_static/<path:file_path>/', views.serve_protected_file, name='serve_protected_file'),
]
In your templates, use the secure URLs:
<a href="{% url 'serve_protected_file' file_path='path/to/your/file.jpg' %}">Download</a>
Additional Security Measures
Token-based access: For added security, generate temporary access tokens that expire after a certain period. Logging and monitoring: Log access to protected files and monitor for unusual activity.
Following these steps ensures that static files are protected and only accessible to authorized users. The combination of Nginx's internal redirects and Django's permission checks allows you to manage user access to URLs securely.
Upvotes: -2
Reputation: 55197
If you are serving small files, you can indeed use Django to serve them directly, writing the file into the HttpResponse
object.
If you're serving large files however, you might want to leave that task to your webserver, you can use the X-Accel-Redirect
header on Nginx (and X-Sendfile
for Apache & Lighttpd) to have your webserver serve the file for you.
You can find more information about the header itself in Nginx's documentation here, and you could find some inspiration as to how to use that in Django here.
Once you're done sending files through Django views, enforcing user authentication should be pretty straightfoward using Django's auth framework.
Upvotes: 10
Reputation: 6503
How about enforcing user==owner
at the view level, preventing access to the files, storing them as FileFields, and only retrieving the file if that condition is met.
e.g. You could use the @login_required
decorator on the view to allow access only if logged in. This could be refined using request.user
to check against the owner of the file. The User Auth section of the Django documentation is likely to be helpful here.
The other option, as you mention is via S3 itself, generating urls within Django which have a querystring allowing an authenticated user access to download a particular s3 object with a time limit. Details on that can be found at the s3 documentation. A similar question has been asked before here on SO.
Upvotes: 3
Reputation: 3138
I've used django-private-files with great success, it enforces protection at the view level and uses differente backends to do the actual file transfer.
Upvotes: 2