I'm trying to figure out a way to restrict access to a media folder in my apache config. The folder takes uploads from from a Django site and image/pdf uploads are displayed in the site to authenticated users. The problem is, that any unauthenticated schmo can navigate to mysite.com/media/images/pic1.jpg
. This shouldn't be possible; I've tried a few things to restrict this behavior, but I think I need a pointer or two.
first try : XSendfile
Xsendfile seemed to work, but it (as the name suggests) sends the file for download, then my page that's supposed to display images doesn't load. So it seems this isn't what I need for my usecase.
second try : rewrite rule
I added some rewrite rules to the apache config:
RewriteCond "%{HTTP_REFERER}" "!^$"
RewriteCond "%{HTTP_REFERER}" "!mysite.com/priv/" [NC]
RewriteRule "\.(gif|jpg|png|pdf)$" "-" [F,NC]
All the parts of the site that requires authentication are behind the /priv/
path, so my idea was that if this works then navigating to /media/images/pic1.jpg
would be rewriten. But this didn't work either mysite.com/media/images/pic1.jpg
still shows the image.
third try : environment
I tried something similar with an environment inside the virtualhost:
<VirtualHost *:80>
...
SetEnvIf Referer "mysite\.com\/priv" localreferer
SetEnvIf Referer ^$ localreferer
<FilesMatch "\.(jpg|png|gif|pdf)$">
Require env localreferer
</FilesMatch>
...
</VirtualHost>
But this also didn't work; I can still navigate directly to the image.
fourth try : Require valid-user
I added Require valid-user
to the v-host, but I can't figure out how to check it against the Django user model. This after this change, I would get a prompt to log in each time I loaded a page which displays images (but w/out htaccess etc, there's nothing to auth against and no images are displayed on the site.
I then tried to implement what is described here (https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/apache-auth/), but my django project doesn't like WSGIHandler
(as opposed to the default get_wsgi_application()
). I get a raise AppRegistryNotReady("Apps aren't loaded yet.")
error. It seems like this might be the most reasonable approach, but I don't know how to get the WSGIHandler
working, or the approach working with the get_wsgi_application()
.
I'm aware that I could give the files a hard-to-guess uuid-like name, but this seems like a half-assed solution. So, what's my best strategy to restrict access to the media folder so that these images are only linked within the part of the site where users are authenticated?
Ubuntu 20.04, Apache 2.4
| Edit, following some advice |
auth.py
def check_password(environ, username, password):
print("---->>>---->>>---->>>---->>>---->>> check_password() has been called <<<----<<<----<<<----<<<----<<<----")
return True
#from django.contrib.auth.handlers.modwsgi import check_password
Apache logs show that this script is loaded, but the function apparently isn't executed as the print statement doesn't turn up in the logs. I put a stray print statement in this file and in the wsgi.py file to make sure this strategy makes it to the logs, only that which was in the wsgi.py file made it to the log.
vhost:
<VirtualHost *:80>
ServerName mysite.com
ServerAlias mysite.com
DocumentRoot /path/to/docroot/
Alias /static/ /path/to/docroot/static/
# Not sure if I need this
Alias /media/ /path/to/docroot/media/
<Directory /path/to/docroot/static/>
Require all granted
</Directory>
<Directory /path/to/docroot/media/>
Require all granted
</Directory>
# this is my restricted access directory
<Directory /path/to/docroot/media/priv/>
AuthType Basic
AuthName "Top Secret"
AuthBasicProvider wsgi
WSGIAuthUserScript /path/to/docroot/mysite/auth.py
Require valid-user
</Directory>
<Directory /path/to/docroot/mysite/>
<Files "wsgi.py">
Require all granted
</Files>
</Directory>
WSGIDaemonProcess open-ancestry-web python-home=/path/to/ENV/ python-path=/path/to/docroot/ processes=10 threads=10
WSGIProcessGroup mysite-pgroup
WSGIScriptAlias / /path/to/docroot/mysite/wsgi.py
LogLevel trace8
ErrorLog "|/bin/rotatelogs -l /path/to/logs/%Y%m%d-%H%M%S_443errors.log 30"
CustomLog "|/bin/rotatelogs -l /path/to/logs/%Y%m%d-%H%M%S_443access.log 30" combined
</VirtualHost>
|another edit |
I accepted the answer because everything is now functional. There were lot's of moving parts, which caused the initial problem with the answer.
(1) The test check_password function wasn't showing up in the apache logs...well it was turning up at /var/log/apache2/error.log
instead of the custom logs that were set up. Not sure why, but ok...
(2) My venv wasn't activated properly and I didn't actually notice this because django is installed on the system Python as well. I copied the activate_this.py
script from a virtualenv and added it to my venv and added sth like this to my wsgi file
activate_this = '/path/to/ENV/bin/activate_this.py'
with open(activate_this) as f:
exec(f.read(), {'__file__': activate_this})
With those things fixed, the check_password function works when called from the wsgi.py file. "works" here means that it restricts access to the folder that unauthed users shouldn't have access to. Users still need to provide credentials twice – once in the regular django view, and once in the browser prompt. This is irritating, but actually my question was about restricting access, so I'll leave it for another day.
The answer's suggestion to call check_password from auth.py is not cooperating with my project. I get errors that suggest it's called before wsgi.py – it seems like the venv is not loaded or the settings are not loaded at the time check_password is called.