
Я пытаюсь найти способ ограничить доступ к папке media в моей конфигурации Apache. Папка принимает загрузки с сайта Django, а загрузки изображений/pdf отображаются на сайте для аутентифицированных пользователей. Проблема в том, что любой неаутентифицированный schmo может перейти в . mysite.com/media/images/pic1.jpg
Это не должно быть возможным; я пробовал несколько вещей, чтобы ограничить это поведение, но я думаю, что мне нужен указатель или два.
первая попытка: XSendfile
Xsendfile, похоже, работает, но он (как следует из названия) отправляет файл на загрузку, а затем моя страница, которая должна отображать изображения, не загружается. Так что, похоже, это не то, что мне нужно для моего варианта использования.
вторая попытка: переписать правило
Я добавил несколько правил перезаписи в конфигурацию Apache:
RewriteCond "%{HTTP_REFERER}" "!^$"
RewriteCond "%{HTTP_REFERER}" "!mysite.com/priv/" [NC]
RewriteRule "\.(gif|jpg|png|pdf)$" "-" [F,NC]
Все части сайта, требующие аутентификации, находятся за путем /priv/
, поэтому моя идея заключалась в том, что если это сработает, то переход на /media/images/pic1.jpg
будет переписан. Но это тоже не сработало, mysite.com/media/images/pic1.jpg
изображение все еще отображается.
третья попытка: окружающая среда
Я попробовал нечто подобное со средой внутри виртуального хоста:
<VirtualHost *:80>
...
SetEnvIf Referer "mysite\.com\/priv" localreferer
SetEnvIf Referer ^$ localreferer
<FilesMatch "\.(jpg|png|gif|pdf)$">
Require env localreferer
</FilesMatch>
...
</VirtualHost>
Но это тоже не сработало; я по-прежнему могу перейти непосредственно к изображению.
четвертая попытка: Требовать действительного пользователя
Я добавил Require valid-user
в v-host, но не могу понять, как проверить его на соответствие модели пользователя Django. После этого изменения я получал запрос на вход каждый раз при загрузке страницы, которая отображает изображения (но без htaccess и т. д. нечему авторизоваться, и на сайте не отображаются изображения).
Затем я попытался реализовать то, что описано здесь (https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/apache-auth/), но мой проект django не любит WSGIHandler
(в отличие от default get_wsgi_application()
). Я получаю raise AppRegistryNotReady("Apps aren't loaded yet.")
ошибку. Кажется, это может быть наиболее разумным подходом, но я не знаю, как заставить его WSGIHandler
работать, или как этот подход работает с get_wsgi_application()
.
Я знаю, что я мог бы дать файлам трудноугадываемое имя типа uuid, но это кажется половинчатым решением. Итак, какова моя лучшая стратегия, чтобы ограничить доступ к папке с медиа, чтобы эти изображения были связаны только в той части сайта, где пользователи аутентифицированы?
Ubuntu 20.04, Apache 2.4
| Редактировать, следуя некоторым советам |
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 показывают, что этот скрипт загружен, но функция, по-видимому, не выполняется, поскольку оператор печати не появляется в логах. Я поместил случайный оператор печати в этот файл и в файл wsgi.py, чтобы убедиться, что эта стратегия попадает в логи, в лог попало только то, что было в файле wsgi.py.
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>
|еще одна правка |
Я принял ответ, потому что теперь все работает. Было много подвижных частей, которые и вызвали первоначальную проблему с ответом. (1) Тестовая функция check_password не отображалась в журналах Apache... ну, она отображалась /var/log/apache2/error.log
вместо настроенных пользовательских журналов. Не уверен, почему, но ладно...
(2) Мой venv не был активирован должным образом, и я на самом деле не заметил этого, потому что django установлен в системе Python также. Я скопировал скрипт activate_this.py
из virtualenv и добавил его в свой venv и добавил что-то вроде этого в свой wsgi-файл
activate_this = '/path/to/ENV/bin/activate_this.py'
with open(activate_this) as f:
exec(f.read(), {'__file__': activate_this})
После исправления этих вещей функция check_password работает при вызове из файла wsgi.py. «Работает» здесь означает, что она ограничивает доступ к папке, к которой неавторизованные пользователи не должны иметь доступа. Пользователям по-прежнему нужно предоставлять учетные данные дважды — один раз в обычном представлении django и один раз в приглашении браузера. Это раздражает, но на самом деле мой вопрос был об ограничении доступа, поэтому я оставлю его на другой день.
Предложение ответа вызвать check_password из auth.py не сотрудничает с моим проектом. Я получаю ошибки, которые предполагают, что он вызывается до wsgi.py – похоже, что venv не загружен или настройки не загружены во время вызова check_password.
решение1
вот что делает get_wsgi_application:
def get_wsgi_application():
django.setup(set_prefix=False) # this will lead to "apps_ready=true"
return WSGIHandler()
он настраивает среду django перед возвратом обработчика.
Следующее должно сработать в вашем wsgi.py:
application = get_wsgi_application()
from django.contrib.auth.handlers.modwsgi import check_password
# the sequence is important!!
На самом деле проблема в первой строке modwsgi.py:
UserModel = auth.get_user_model()
потому что get_user_model() проверит apps_ready и все это будет сделано в тот момент, когда python выполнит импорт файла!
Лучшим способом было бы создать отдельный auth.py и сначала проверить, действительно ли он вызывается Apache, с помощью простого вывода, который будет отправлен в error.log Apache:
def check_password(environ, username, password):
print("*********** check_password() has been called ********")
return True
После запуска вы можете заменить его оператором import и использовать djangos check_password().
from django.contrib.auth.handlers.modwsgi import check_password
Затем в httpd-vhosts.conf что-то вроде следующего:
<VirtualHost *:80>
....
<Directory path_to_server_root/secret>
AuthType Basic
AuthName "Top Secret"
AuthBasicProvider wsgi
WSGIAuthUserScript path_to_wsgi/wsgi.py
Require valid-user
</Directory>
</VirtualHost>