I'm fairly new to django and nginx, and I started using DigitalOcean's one-click-image install Django 1.6 environment which uses nginx and gunicorn. The default specifications and details can be found here https://www.digitalocean.com/community/tutorials/how-to-use-the-django-one-click-install-image.
Recently, I have been trying to figure out how to secure the admin page for deployment with SSL/HTTPS to prevent login capture. And I realize there is information on this on stackoverflow already. However, it seems like most information has base settings for nginx that differ from Digital Oceans. So, I'm a little thrown for what I should keep, edit and delete. At this moment I'm satisfied with proxy over two instances in nginx: flat pages being under HTTP and the admin under HTTPS as this is simply going to be a launch page. How would you secure the entire site behind https? What would be the necessary modifications to the following nginx and Django settings?
Nginx Settings
upstream app_server {
server 127.0.0.1:9000 fail_timeout=0;
}
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
root /usr/share/nginx/html;
index index.html index.htm;
client_max_body_size 4G;
server_name _;
keepalive_timeout 5;
# Your Django project's media files - amend as required
location /media {
alias /home/django/django_project/media;
}
# your Django project's static files - amend as required
location /static {
alias /home/django/django_project/static;
}
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://app_server;
}
}
Django Settings
"""Production settings and globals."""
from __future__ import absolute_import
from os import environ
from .base import *
SESSION_COOKIE_SECURE = True
CRSF_COOKIE_SECURE = True
# Honor the 'X-Fowarded-Proto' header for request.is_secure()
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
########## HOST CONFIGURATION
# See: https://docs.djangoproject.com/en/1.5/releases/1.5/#allowed-hosts-required-in- production
ALLOWED_HOSTS = [
'localhost',
'{{website url}}',
]
########## END HOST CONFIGURATION
########## EMAIL CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
# See: https://docs.djangoproject.com/en/dev/ref/settings/#email-host
EMAIL_HOST = environ.get('EMAIL_HOST', 'smtp.gmail.com')
# See: https://docs.djangoproject.com/en/dev/ref/settings/#email-host-password
EMAIL_HOST_PASSWORD = environ.get('EMAIL_HOST_PASSWORD', '')
# See: https://docs.djangoproject.com/en/dev/ref/settings/#email-host-user
EMAIL_HOST_USER = environ.get('EMAIL_HOST_USER', 'your_email#example.com')
# See: https://docs.djangoproject.com/en/dev/ref/settings/#email-port
EMAIL_PORT = environ.get('EMAIL_PORT', 587)
# See: https://docs.djangoproject.com/en/dev/ref/settings/#email-subject-prefix
EMAIL_SUBJECT_PREFIX = '[%s] ' % SITE_NAME
# See: https://docs.djangoproject.com/en/dev/ref/settings/#email-use-tls
EMAIL_USE_TLS = True
# See: https://docs.djangoproject.com/en/dev/ref/settings/#server-email
SERVER_EMAIL = EMAIL_HOST_USER
########## END EMAIL CONFIGURATION
########## DATABASE CONFIGURATION
DATABASES = {}
########## END DATABASE CONFIGURATION
########## CACHE CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#caches
CACHES = {}
########## END CACHE CONFIGURATION
########## SECRET CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#secret-key
SECRET_KEY = get_secret("SECRET_KEY")
########## END SECRET CONFIGURATION
Related
I am trying to use django-private-storage package to protect a model's file from being accessed
or downloaded by users who are not owners of the file.
I was able to do it successfully in development (using python manage.py runserver)
In development, I am using nginx configured via docker.
I can create objects with both FileField and PrivateFileField. My problem is accessing the urls associated with a PrivateFileField.
The mediafiles are being served as expected (e.g. when I access the url of a FileField), but I get a "404 Not Found" error from nginx when I access the url of a PrivateFileField.
My hunch is that the server response is not properly configured to have a 'X-Accel-Redirect' data,
thus treating the response not internal.
If I remove the line "internal;" in my nginx.conf for the private-data location, the PrivateFile is served
properly, although, now, it is not private.
location /private-data/ {
internal; #<------ the PrivateFile can be accessed if this line is removed
alias /home/app/web/private-data/;
}
Also, I am sure that the private file was saved in /home/app/web/private-data
Am I missing out something in the implementation?
Thanks in advance.
Additional info:
nginx version: 1.17.4
django-private-storage version: 2.2.2
My production setup follows this:
https://testdriven.io/blog/dockerizing-django-with-postgres-gunicorn-and-nginx/
FILES -----------------------------------
docker-compose.prod.yml
version: '3.7'
services:
web:
build:
context: ./web_app
dockerfile: Dockerfile.prod
command: gunicorn notify_django_project.wsgi:application --bind 0.0.0.0:8000
volumes:
- static_volume:/home/app/web/staticfiles
- media_volume:/home/app/web/mediafiles
- private_volume:/home/app/web/private-data
expose:
- 8000
env_file:
- ./.env.prod
depends_on:
- db
db:
image: postgres:12.0-alpine
volumes:
- postgres_data:/var/lib/postgresql/data/
env_file:
- ./.env.prod.db
nginx:
build: ./nginx
volumes:
- static_volume:/home/app/web/staticfiles
- media_volume:/home/app/web/mediafiles
- private_volume:/home/app/web/private-data
ports:
- 1337:80
depends_on:
- web
volumes:
postgres_data:
static_volume:
media_volume:
private_volume:
nginx.conf
upstream django_project {
server web:8000;
}
server {
listen 80;
location / {
proxy_pass http://django_project;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_redirect off;
}
location /staticfiles/ {
alias /home/app/web/staticfiles/;
}
location /mediafiles/ {
alias /home/app/web/mediafiles/;
}
location /private-data/ {
internal;
alias /home/app/web/private-data/;
}
}
settings.py
INSTALLED_APPS = [
...
'private_storage',
....
]
PRIVATE_STORAGE_ROOT = os.path.join(BASE_DIR, "private-data")
PRIVATE_STORAGE_AUTH_FUNCTION = 'private_storage.permissions.allow_authenticated'
PRIVATE_STORAGE_INTERNAL_URL = '/private-data/'
PRIVATE_STORAGE_SERVER = 'nginx'
models.py
class Message(models.Model):
id = models.UUIDField(
primary_key=True,
default=uuid.uuid4,
editable=False)
subject = models.CharField(max_length=255)
attachment = models.FileField(upload_to=get_attachment_save_path, null=True, blank=True)
private_attachment = PrivateFileField(upload_subfolder=get_private_attachment_save_path, null=True, blank=True)
urls.py
urlpatterns = [
...
path('private-data/<str:code>/<int:year>/<str:subdir>/<uuid:pk>/<str:filename>', DownloadPrivateFileView.as_view(), name="file_download"),
url('^private-data/', include(private_storage.urls)),
...
]
views.py
#method_decorator(login_required, name='dispatch')
class DownloadPrivateFileView(PrivateStorageDetailView):
model = Message
model_file_field = 'private_attachment'
def can_access_file(self, private_file):
# When the object can be accessed, the file may be downloaded.
# This overrides PRIVATE_STORAGE_AUTH_FUNCTION
# grant_access checks private_file ownership
grant_access = grant_note_access(private_file.request, message=self.get_object())
return grant_access
Iam in the testing phase with this library too!
I did not use these lines that indicated, which serves to speed up uploading large files.
without these settings and debug = False, everything worked correctly.
Take the test please.
Sorry for the Translation, I don't speak English
My conf for my site in nginx mysite.conf
server {
listen 80;
server_name mysite.com.br;
return 301 https://mysite.com.br$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name mysite.com.br;
location ~ ^/.well-known{
root /var/www/myapp;
}
location / {
proxy_pass http://127.0.0.1:6010; # My container is at port 3020
}
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
ssl_session_tickets off;
# Certificate free letsencrypt
ssl_certificate /etc/letsencrypt/live/mysite.com.br/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mysite.com.br/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
add_header Strict-Transport-Security "max-age=63072000" always;
# OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;
}
My setting.py for django:
MEDIA_ROOT = os.path.join(BASE_DIR, 'anexosapp') # Folder for files private app
MEDIA_URL = '/docs/'
# Conf for django-private-storage
INSTALLED_APPS += (
'private_storage',
)
PRIVATE_STORAGE_ROOT = os.path.join(BASE_DIR, 'anexosapp/')
PRIVATE_STORAGE_AUTH_FUNCTION = 'private_storage.permissions.allow_authenticated' # allow user authenticated
# settings for static server over whitenoise
STATIC_URL = '/static/'
STATICFILES_DIRS = [os.path.join(BASE_DIR, "static"), ]
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
STATICFILES_STORAGE = 'whitenoise.storage.CompressedStaticFilesStorage'
urls.py app main:
urlpatterns += [
path('private-media/', include(private_storage.urls)),
]
The file access url looks like this
https : //mysite.com.br/private-media/docs/namefile.pdf
If anyone finds errors, tips for improving the settings, please indicate! Thank you
I just removed from nginx.conf location that refers to private media like so:
upstream "my domain" {
server web:8000;
}
server {
listen 80;
location / {
proxy_pass http://"my domain";
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_redirect off;
}
location /staticfiles/ {
alias /home/app/web/staticfiles/;
}
location /mediafiles/ {
alias /home/app/web/mediafiles/;
}
#location /privatefiles/ {
# alias /home/app/web/privatefiles/;
#}
# Error & Access logs
error_log /home/app/logs/error.log error;
access_log /home/app/logs/access.log;
client_max_body_size 128m;
}
Don't forget to set DEBUG = False, then it should worked perfectly. Every time I tried to access to private media without login in, it redirected me to login page.
This is my settings.py
...
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static")
]
STATIC_URL = "/staticfiles/"
STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")
MEDIA_URL = "/mediafiles/"
MEDIA_ROOT = os.path.join(BASE_DIR, "mediafiles")
PRIVATE_STORAGE_ROOT = os.path.join(BASE_DIR, "privatefiles")
PRIVATE_STORAGE_AUTH_FUNCTION = 'private_storage.permissions.allow_authenticated'
...
and my main urls.py
from django.conf.urls.static import static
from django.conf.urls import url
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
import private_storage.urls <--- this is for private storage
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('core.urls')),
path('rubric/', include('rubric.urls')),
path('warehouse/', include('warehouse.urls')),
path('preoffers/', include('preoffers.urls')),
path('offers/', include('offers.urls')),
url('^privatefiles/', include(private_storage.urls)) <--- this is for private storage
]
if settings.DEBUG:
urlpatterns += static(
settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += static(
settings.STATIC_URL, document_root=settings.STATIC_ROOT)
'internal' param tells nginx it's not accessible from the outside.
We access application and then redirect to nginx
settings.py
PRIVATE_STORAGE_ROOT = os.path.join(BASE_DIR, 'private-media')
PRIVATE_STORAGE_AUTH_FUNCTION = 'private_storage.permissions.allow_staff'
PRIVATE_STORAGE_SERVER = 'nginx'
PRIVATE_STORAGE_INTERNAL_URL = '/private-x-accel-redirect/'
nginx
location /private-x-accel-redirect/ {
internal;
alias /var/www/private-media/;
}
I'm using Django 2.2.x and DRF.
I have a model with FileField
file = models.FileField(upload_to=get_media_upload_path)
Files are uploading but on access the obj.file, it gives URL without HTTPS
http://example.com/media/image.jpg
I want it to be
https://example.com/media/image.png
Redirect is already setup in nginx config. But I want the response URL with https.
settings
MEDIA_URL = '/media_/'
MEDIA_ROOT = os.path.join(os.path.dirname(BASE_DIR), 'static_cdn', 'media_root')
First, make sure Nginx is sending the X-Forwarded-Proto header, it should be set to:
proxy_set_header X-Forwarded-Proto https;
Then in your Django settings add the following:
USE_X_FORWARDED_HOST = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
In this way you will instruct Django to use the proto passed by the proxy.
I'm deploying my Wagtail application to production but could not get it to work. I'm following this tutorial https://www.digitalocean.com/community/tutorials/how-to-set-up-django-with-postgres-nginx-and-gunicorn-on-ubuntu-18-04.
The website is work as expected on my server when using development settings, as soon as I changed to production it won't work.
On production, I'm only able to access the admin and sitemap URL.
production.py
from .base import *
with open('/etc/secret_key.txt') as f:
SECRET_KEY = f.read().strip()
DEBUG = False
try:
from .local import *
except ImportError:
pass
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'name',
'USER': 'user',
'PASSWORD': 'password',
'HOST': 'localhost',
'PORT': '',
}
}
# SECURITY WARNING: define the correct hosts in production!
INTERNAL_IPS = ("127.0.0.1")
ALLOWED_HOSTS = ['*']
nginx
# Redirect unencrypted traffic to the encrypted site
server {
listen 80;
server_name domain www.domain IP;
return 301 https://domain$request_uri;
}
server {
client_max_body_size 20M;
listen 443 default ssl;
server_name domain.com IP;
keepalive_timeout 70;
ssl on;
ssl_certificate /etc/nginx/certs.pem;
ssl_certificate_key /etc/nginx/privkey.pem;
location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
root /home/user/projectdir;
}
location / {
include proxy_params;
proxy_pass http://unix:/run/gunicorn.sock;
}
}
Gunicorn.service
[Unit]
Description=gunicorn daemon
Requires=gunicorn.socket
After=network.target
[Service]
User=sherpa
Group=www-data
WorkingDirectory=/home/user/projectdir
ExecStart=/home/user/projectenv/bin/gunicorn \
--access-logfile - \
--workers 3 \
--bind unix:/run/gunicorn.sock \
project.wsgi_production:application
[Install]
WantedBy=multi-user.target
urls.py
urlpatterns = [
url(r'^django-admin/', admin.site.urls),
url(r'^admin/', include(wagtailadmin_urls)),
url(r'^documents/', include(wagtaildocs_urls)),
url(r'^search/$', search_views.search, name='search'),
url(r'^sitemap.xml', sitemap),
# For anything not caught by a more specific rule above, hand over to
# Wagtail's page serving mechanism. This should be the last pattern in
# the list:
url(r'', include(wagtail_urls)),
# Alternatively, if you want Wagtail pages to be served from a subpath
# of your site, rather than the site root:
# url(r'^pages/', include(wagtail_urls)),
]
I'm having the same issue. If I set to Debug = True, everything works fine. If I set Debug = False I get the 500 error page, but nothing in the logs to tell me an error has been fired. I am using Apache and not nginx.
I have deployed Django with Gunicorn and NGINX, and it works fine, if the Django app is served on the root url, with this configuration:
server {
listen 80;
location = /favicon.ico { access_log off; log_not_found off; }
location / {
include proxy_params;
proxy_pass http://unix:my_app.sock;
}
}
However when I try to serve the Django app on another URL, it doesn't work.
If I try to access http://domain/my_app/admin/ then Django tells me it cannot find the view.
This is the NGINX config:
server {
listen 80;
location = /favicon.ico { access_log off; log_not_found off; }
location /my_app {
include proxy_params;
proxy_pass http://unix:/var/my_app/app.sock;
}
}
How could I make this work? I was not able to find any solution to specify something like a "BASE_URL" so far.
My comment doesn't show the whole picture. When I've run Django sites on subfolders, I like to use a dynamic config so that you can still access the machine directly (without the proxy) and have a working web-app. This can help a LOT for debugging tricky stuff like this that is hard to reproduce in dev.
If you do not have the ability to pass the header or modify wsgi.py, you can still set FORCE_SCRIPT_NAME in your Django settings.
3 steps:
set up a proxy in front of the webserver that strips the subfolder out of the URL
set the X-Script-Name header so that your Django site generates its urls with /myapp/ infront of them -- make sure you're using the {% url %} tag and reverse, vs. hard-coding!
modify myapp/wsgi.py to read a new header X-Script-Name into the wsgi environment variable SCRIPT_NAME (based on this flask snippet)
Here is an example Nginx config for a proxy that points to a Django site on a subdirectory and also sets X-Script-Name (steps 1 and 2), note that this doesn't use a unix socket, so it's a little different from the OP's question. Would welcome an edit:
nginx.conf
location /my_app {
proxy_pass https://mywebapp.com/;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Script-Name /my_app;
proxy_cookie_path / /my_app;
}
And to read X-Script-Name:
myapp/wsgi.py
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myapp.settings")
_application = get_wsgi_application()
def application(environ, start_response):
# http://flask.pocoo.org/snippets/35/
script_name = environ.get('HTTP_X_SCRIPT_NAME', '')
if script_name:
environ['SCRIPT_NAME'] = script_name
path_info = environ['PATH_INFO']
if path_info.startswith(script_name):
environ['PATH_INFO'] = path_info[len(script_name):]
scheme = environ.get('HTTP_X_SCHEME', '')
if scheme:
environ['wsgi.url_scheme'] = scheme
return _application(environ, start_response)
#andyC thank you for your answer.
With up-to-date Django (4.1.4) it worked with the following settings. I made the following setting in nginx for django, which is behind the api gateway.
settings.py
USE_X_FORWARDED_HOST = True
FORCE_SCRIPT_NAME = '/content/'
SESSION_COOKIE_PATH = '/content/'
LOGIN_URL = "login/"
LOGIN_REDIRECT_URL = '/content/'
LOGOUT_REDIRECT_URL = '/content/'
STATIC_URL = '/content/static/'
STATIC_ROOT = os.path.join(BASE_DIR, "static/")
# Media files
MEDIA_URL = '/content/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, "mediafiles/")
apiGateway -> nginx.conf
server_name api.mydomain.com;
location /auth/ {
proxy_pass http://django_app:8002/;
proxy_set_header Host $http_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;
}
if you run django with gunicorn and use uvicorn as asgi worker, like
gunicorn example:asgi.app -w 4 -k uvicorn.workers.UvicornWorker
you need to add root_path='/api/' to django asgi scope, unfortunately, gunicorn can't pass this thought arguments but you can write a custom worker and run gunicorn with it
class MyUvicornWorker(UvicornWorker):
CONFIG_KWARGS = {"loop": "auto", "http": "auto", "root_path": "/api/"}
and then run gunicorn with custom worker
gunicorn example:asgi.app -w 4 -k example.workers.MyUvicornWorker
Here is what I solve this problem. I can run multiple django running on same server/domain with subdirectory.
project/settings.py
MY_PROJECT = env('MY_PROJECT', '') # example; '/dj'
if MY_PROJECT:
USE_X_FORWARDED_HOST = True
FORCE_SCRIPT_NAME = MY_PROJECT + "/"
SESSION_COOKIE_PATH = MY_PROJECT + "/"
LOGIN_URL = "login/"
LOGIN_REDIRECT_URL = MY_PROJECT + "/"
LOGOUT_REDIRECT_URL = MY_PROJECT + "/"
NGINX configuration
location /dj/ {
proxy_set_header X-Forwarded-Protocol $scheme;
proxy_set_header Host $http_host;
proxy_set_header X-Scheme $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:8000/;
}
Be aware that location url should end with "/"
i have try every configuration that i found and the static files don't get loaded, so i doesn't know what's wrong. I create the app using Pycharm.
Can someone give me a hand to fix this?
Here are the configurations.
settings.py
MEDIA_ROOT = ''
MEDIA_URL = '/Users/gcarranza/PycharmProjects/clouddemyTest/'
STATIC_ROOT = ''
STATIC_URL = '/Users/gcarranza/PycharmProjects/clouddemyTest/static/'
STATICFILES_DIRS = (
'/Users/gcarranza/PycharmProjects/clouddemyTest/static',)
nginx conf:
server {
listen 80;
server_name localhost;
location /static/ {
autoindex on;
alias /Users/gcarranza/PycharmProjects/clouddemyTest/static/;
}
location / {
proxy_pass http://127.0.0.1:8000;
}
}
Gunicorn:
gunicorn_django -b localhost:8000
You got the root and url backwards. Should be:
STATIC_ROOT = '/Users/gcarranza/PycharmProjects/clouddemyTest/static/'
STATIC_URL = '/static/'
And same for media