From ce05258ca47716f0c39519a92937e77b9007b9d3 Mon Sep 17 00:00:00 2001 From: Tobias Bengfort Date: Tue, 27 Apr 2021 11:54:29 +0200 Subject: [PATCH 1/2] implement protected media using X-Sendfile --- castellum/settings/default.py | 17 +++++++++++++++++ castellum/settings/development.py | 1 + castellum/urls.py | 25 +++++++++++++++++++++++-- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/castellum/settings/default.py b/castellum/settings/default.py index 7a26d7db3..e553c6513 100644 --- a/castellum/settings/default.py +++ b/castellum/settings/default.py @@ -180,6 +180,23 @@ MEDIA_URL = '/media/' MEDIA_ROOT = BASE_DIR / 'media' +# By default, castellum does not serve media (aka "uploaded") files. +# You have 3 options here: +# +# - Set PROTECTED_MEDIA_SERVER to "django" (only for development) +# - Set PROTECTED_MEDIA_SERVER to None and let a proxy serve the files +# instead. This is insecure because it bypasses castellum login. +# - Set PROTECTED_MEDIA_SERVER to the proxy server you use (e.g. +# 'nginx', 'uwsgi') to use X-Sendfile. You will have to configure +# the proxy accordingly. This option is strongly recommended. +# +# references: +# - https://docs.djangoproject.com/en/3.2/ref/contrib/staticfiles/#django.contrib.staticfiles.views.serve +# - https://www.nginx.com/resources/wiki/start/topics/examples/xsendfile/ +# - https://uwsgi-docs.readthedocs.io/en/latest/Snippets.html#x-sendfile-emulation +PROTECTED_MEDIA_SERVER = None +PROTECTED_MEDIA_URL = '/protected/' + NPM_ROOT_PATH = BASE_DIR.parent NPM_FILE_PATTERNS = { diff --git a/castellum/settings/development.py b/castellum/settings/development.py index 64f07f12d..aa1c78927 100644 --- a/castellum/settings/development.py +++ b/castellum/settings/development.py @@ -7,6 +7,7 @@ SESSION_COOKIE_SECURE = False DEBUG = True +PROTECTED_MEDIA_SERVER = 'django' # setup debug toolbar diff --git a/castellum/urls.py b/castellum/urls.py index a3a26c2de..d47bdcd57 100644 --- a/castellum/urls.py +++ b/castellum/urls.py @@ -19,8 +19,11 @@ # License along with Castellum. If not, see # . +import mimetypes + from django.conf import settings from django.contrib import admin +from django.contrib.auth.decorators import login_required from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.views import LoginView from django.contrib.auth.views import LogoutView @@ -54,6 +57,24 @@ class FeedsView(LoginRequiredMixin, TemplateView): return context +@login_required +def protected_media(request, path): + if settings.PROTECTED_MEDIA_SERVER == 'django': + return serve(request, path, document_root=settings.MEDIA_ROOT) + elif settings.PROTECTED_MEDIA_SERVER == 'nginx': + response = HttpResponse() + response['X-Accel-Redirect'] = settings.PROTECTED_MEDIA_URL + path + return response + else: + mimetype, encoding = mimetypes.guess_type(settings.MEDIA_ROOT / path) + response = HttpResponse() + response['Content-Type'] = mimetype + if encoding: + response['Content-Encoding'] = encoding + response['X-Sendfile'] = settings.MEDIA_ROOT / path + return response + + def dummy(request): return HttpResponse('', status=204) @@ -84,9 +105,9 @@ urlpatterns = [ ), ] -if settings.DEBUG: +if settings.PROTECTED_MEDIA_SERVER: urlpatterns += [ - path('media/', serve, {'document_root': settings.MEDIA_ROOT}), + path('media/', protected_media), ] try: -- GitLab From cc96b4ee52b2129cc0c27712fcb2f5262415144c Mon Sep 17 00:00:00 2001 From: Tobias Bengfort Date: Tue, 27 Apr 2021 11:55:01 +0200 Subject: [PATCH 2/2] enable protected media in example deployment --- Dockerfile | 2 +- docs/example_deployment/settings.py | 3 ++- docs/example_deployment/uwsgi.ini | 8 +++++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index af92f6616..0ac59cabd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ FROM alpine:3.13.4 ENV PYTHONUNBUFFERED 1 ENV BUILDPKGS gettext py3-pip py3-wheel -ENV RUNTIMEPKGS uwsgi uwsgi-python python3 py3-psycopg2 py3-pyldap libmagic proj gdal +ENV RUNTIMEPKGS uwsgi uwsgi-python uwsgi-router_static python3 py3-psycopg2 py3-pyldap libmagic proj gdal RUN adduser -D -g '' uwsgi diff --git a/docs/example_deployment/settings.py b/docs/example_deployment/settings.py index c67bc88c5..cf6fcb87c 100644 --- a/docs/example_deployment/settings.py +++ b/docs/example_deployment/settings.py @@ -28,7 +28,8 @@ DATABASES = { ALLOWED_HOSTS = ['*'] -MEDIA_ROOT = '/media' +MEDIA_ROOT = Path('/media') +PROTECTED_MEDIA_SERVER = 'uwsgi' # The example deployment does not contain a mail server, so use dummy instead EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' diff --git a/docs/example_deployment/uwsgi.ini b/docs/example_deployment/uwsgi.ini index d9f1e02a5..1127b3637 100644 --- a/docs/example_deployment/uwsgi.ini +++ b/docs/example_deployment/uwsgi.ini @@ -9,8 +9,14 @@ socket=0.0.0.0:8000 # enables internal http server and router protocol=http static-map=/static=/code/castellum/collected_static -static-map=/media=/media static-map=/favicon.ico=/code/castellum/collected_static/images/favicon.ico +# https://uwsgi-docs.readthedocs.io/en/latest/Snippets.html#x-sendfile-emulation +plugins = router_static +offload-threads = 2 +static-safe = /media +collect-header = X-Sendfile X_SENDFILE +response-route-if-not = empty:${X_SENDFILE} static:${X_SENDFILE} + wsgi-file=castellum/wsgi.py plugin=python -- GitLab