×


Harden the security of Django

Are you trying to harden the security of Django?

This guide is for you.


Developing a Django application can be quick and clean because its approach is flexible and scalable.

However, when it comes to production deployment, there are several ways to further secure the project.

Here at Ibmi Media, as part of our Server Management Services, we regularly help our Customers to perform Django related queries.

In this context, we shall look into how to harden the security of Django.


How to improve Security of Django ?

Even though developing a Django application is a quick and clean experience, the production deployment requires several ways to further secure the project.

In order to secure the project, we can restructure it by breaking up the settings. This will allow us to easily set up different configurations based on the environment.


Similarly, leveraging dotenv for hiding environment variables or confidential settings will ensure that we do not release any details that may compromise the project.

Implementation of these strategies and features might seem time-consuming. However, developing a practical workflow will allow us to deploy releases of the project without compromising on security or productivity.


Here, we will leverage a security-oriented workflow for Django development by implementing and configuring environment-based settings, dotenv, and Django's built-in security settings.

All these features complement each other and will result in a version of the Django project that is ready for different approaches we may take to deployment.


In order to begin, our Support Experts suggest having a pre-existing Django project.


How to Harden the security of Django ?

Here, we will use the testsite project as an example. An existing Django project may have different requirements. Let us discuss in detail how to harden the security of Django.


Step 1: Restructure Django's Settings

Initially, let us rearrange the settings.py file into environment-specific configurations.

This arrangement will mean less reconfiguration for different environments; instead, we will use an environment variable to switch between configurations.

We need to create a new directory called settings in the project's sub-directory:

$ mkdir testsite/testsite/settings

This directory will replace the current settings.py configuration file.

Once done, we create three Python files:

$ cd testsite/testsite/settings
$ touch base.py development.py production.py

The development.py file contains settings we use during development. The production.py contains settings for use on a production server.

We need to keep these separate since the production configuration will use settings that will not work in a development environment.

The base.py settings file contains settings that development.py and production.py will inherit from. This is to reduce redundancy and to help keep the code cleaner.

These Python files will be replacing settings.py, so we will now remove settings.py to avoid confusing Django.

We rename settings.py to base.py with the following command:

$ mv ../settings.py base.py

We have now come to the end of creating the outline of the new environment-based settings directory.


Step 2: Use python-dotenv

At the moment, Django will fail to recognize the new settings directory or its internal files. So, before we continue, we need to make Django work with python-dotenv.

This is a dependency that loads environment variables from a .env file. Django looks inside a .env file in the project’s root directory to determine which settings configuration it will use.

Initially, we go to the project's root directory:

$ cd ../../

Then we install python-dotenv:

$ pip install python-dotenv

Now we need to configure Django to use dotenv. In order to do this, we will edit, manage.py, for development, and wsgi.py, for production.

Let us start by editing manage.py:

$ nano manage.py

Add the following code:

import os
import sys
import dotenv
def main():
os.environ.setdefault(‘DJANGO_SETTINGS_MODULE’, ‘testsite.settings.development’)
if os.getenv(‘DJANGO_SETTINGS_MODULE’):
os.environ[‘DJANGO_SETTINGS_MODULE’] = os.getenv(‘DJANGO_SETTINGS_MODULE’)


try:

from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
“Couldn’t import Django. Are you sure it’s installed and ”
“available on your PYTHONPATH environment variable? Did you ”
“forget to activate a virtual environment?”
) from exc
execute_from_command_line(sys.argv)
if __name__ == ‘__main__’:
main()
dotenv.load_dotenv(
os.path.join(os.path.dirname(__file__), ‘.env’)
)

Save and close manage.py and then open wsgi.py for editing:

$ nano testsite/wsgi.py

Add the following highlighted lines:

import os
import dotenv
from django.core.wsgi import get_wsgi_application
dotenv.load_dotenv(
os.path.join(os.path.dirname(os.path.dirname(__file__)), ‘.env’)
)
os.environ.setdefault(‘DJANGO_SETTINGS_MODULE’, ‘testsite.settings.development’)
if os.getenv(‘DJANGO_SETTINGS_MODULE’):
os.environ[‘DJANGO_SETTINGS_MODULE’] = os.getenv(‘DJANGO_SETTINGS_MODULE’)
application = get_wsgi_application()

The code we have added looks for .env file.

If the file exists, we instruct Django to use the settings file that .env recommends, otherwise, we use the development configuration by default.

Save and close the file.

Finally, let us create a .env in the project’s root directory:

$ nano .env

Now add in the following line to set the environment to development:

DJANGO_SETTINGS_MODULE="testsite.settings.development"

Add .env to .gitignore file, to use this file to contain data such as passwords and API keys that we do not want visible publicly. Every environment the project is running on will have its own .env with settings for that specific environment.

Our Support Experts recommend creating a .env.example to include in the project. It will help to easily create a new .env wherever we need one.

So, by default Django will use testsite.settings.development, but if we change DJANGO_SETTINGS_MODULE to testsite.settings.production, it will start using the production configuration.


Step 3: Create Development and Production Settings

Moving ahead, we will open base.py and add the configurations to modify for each environment in separate development.py and production.py files.

Ensure that the production.py has the production database credentials.

We can determine the settings to configure, based on the environment. Here, we will only cover a common example for production and development settings.

Initially, we will move settings from base.py to development.py. To do that, open development.py:

$ nano testsite/settings/development.py

First, we will import from base.py – this file inherits settings from base.py. Then we will transfer the settings we want to modify for the development environment:

from .base import *
DEBUG = True
DATABASES = {
‘default’: {
‘ENGINE’: ‘django.db.backends.sqlite3’,
‘NAME’: os.path.join(BASE_DIR, ‘db.sqlite3’),
}
}

In this case, the settings specific to development are: DEBUG, we need this True in development, but not in production; and DATABASES, a local database instead of a production database. We are using an SQLite database here for development.

For security purposes, Django's DEBUG output will never display any settings that may contain the strings: API, KEY, PASS, SECRET, SIGNATURE, or TOKEN.

Next, let us add to production.py:

$ nano testsite/settings/production.py

Production will be similar to development.py, but with a different database configuration and DEBUG set to False:

from .base import *
DEBUG = False
ALLOWED_HOSTS = []
DATABASES = {
‘default’: {
‘ENGINE’: os.environ.get(‘SQL_ENGINE’, ‘django.db.backends.sqlite3’),
‘NAME’: os.environ.get(‘SQL_DATABASE’, os.path.join(BASE_DIR, ‘db.sqlite3’)),
‘USER’: os.environ.get(‘SQL_USER’, ‘user’),
‘PASSWORD’: os.environ.get(‘SQL_PASSWORD’, ‘password’),
‘HOST’: os.environ.get(‘SQL_HOST’, ‘localhost’),
‘PORT’: os.environ.get(‘SQL_PORT’, ”),
}
}

For the example database configuration given, we can use dotenv to configure each of the given credentials, with defaults included.

Hence we have configured the project to use different settings based on DJANGO_SETTINGS_MODULE in .env.

Given the example settings here, when we set the project to use production settings, DEBUG becomes False, ALLOWED_HOSTS is defined, and we start using a different database configured on the server.


Step 4: Work with Django's Security Settings

Django includes security settings ready for us to add to the project. These settings are intended for use when the project is available to the public.


Our Support Experts does not recommend using any of these settings in the development environment. Hence, we limit these settings to the production.py configuration.

These settings are going to enforce the use of HTTPS for various web features, such as session cookies, upgrading HTTP to HTTPS, and so on. Therefore, if the server is not set up with a domain pointing to it, hold off on this section for now.

First, we open production.py:

$ nano production.py

In the file, add the highlighted settings that work for the project, according to the explanations following the code:

from .base import *
DEBUG = False
ALLOWED_HOSTS = [‘your_domain’, ‘www.your_domain’]
DATABASES = {
‘default’: {
‘ENGINE’: os.environ.get(‘SQL_ENGINE’, ‘django.db.backends.sqlite3’),
‘NAME’: os.environ.get(‘SQL_DATABASE’, os.path.join(BASE_DIR, ‘db.sqlite3’)),
‘USER’: os.environ.get(‘SQL_USER’, ‘user’),
‘PASSWORD’: os.environ.get(‘SQL_PASSWORD’, ‘password’),
‘HOST’: os.environ.get(‘SQL_HOST’, ‘localhost’),
‘PORT’: os.environ.get(‘SQL_PORT’, ”),
}
}
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_BROWSER_XSS_FILTER = True
SECURE_SSL_REDIRECT

This redirects all HTTP requests to HTTPS. Hence, the project will always try to use an encrypted connection. We need SSL configured on the server for this to work. However, if we have Nginx or Apache configured to do this already, this setting will be redundant.


SESSION_COOKIE_SECURE

This tells the browser that cookies can only be handled over HTTPS. Hence, cookies that the project produces for activities, such as logins, will only work over an encrypted connection.


CSRF_COOKIE_SECURE

It is the same as SESSION_COOKIE_SECURE but applies to the CSRF token. Django CSRF protection protects against Cross-Site Request Forgery by ensuring that the forms submitted to the project were created by the project and not a third party.


SECURE_BROWSER_XSS_FILTER

This sets the X-XSS-Protection: 1; mode=block header on all responses that do not already have it. This ensures third parties cannot inject scripts into the project.


Step 5: Use python-dotenv for Secrets

The final section will help us leverage python-dotenv. This allows us to hide certain information such as the project’s SECRET_KEY or the admin’s login URL.

This is a great idea if we intend to publish the code on platforms like GitHub or GitLab since these secrets will not be published.

Instead, whenever we initially set up the project on a local environment or a server, we can create a new .env file and define those secret variables.

We must hide SECRET_KEY so we will start working on that in this section.


Initially, open .env file:

$ nano .env

And add the following line:

DJANGO_SETTINGS_MODULE=”django_hardening.settings.development”
SECRET_KEY=”your_secret_key”

Then in base.py:

$ nano testsite/settings/base.py

Let us update the SECRET_KEY variable:

. . .
SECRET_KEY = os.getenv(‘SECRET_KEY’)
. . .

The project will now use the SECRET_KEY located in .env.

Finally, we will hide the admin URL by adding a long string of random characters to it. This will ensure bots cannot brute force the login fields and strangers cannot try guessing the login.


Open .env again:

$ nano .env

Then add a SECRET_ADMIN_URL variable:

DJANGO_SETTINGS_MODULE=”django_hardening.settings.development”
SECRET_KEY=”your_secret_key”
SECRET_ADMIN_URL=”very_secret_url”

Now we will tell Django to hide the admin URL with SECRET_ADMIN_URL:

$ nano /testsite/urls.py

Do not forget to replace your_secret_key and very_secret_url with our own secret strings.


Python provides a fantastic secrets.py library for generating random strings for these variables.

Edit the admin URL like so:

import os
from django.contrib import admin
from django.urls import path
urlpatterns = [
path(os.getenv(‘SECRET_ADMIN_URL’) + ‘/admin/’, admin.site.urls),
]

We can now find the admin login page at very_secret_url/admin/ instead of just /admin/.


[Stuck with the procedures to secure Django Applications? We'd be happy to assist you. ]


Conclusion

This article will guide you on steps to #harden the #security of #Django. We leveraged python-dotenv for handling secrets and settings.

Our sensitive mostly kept in settings.py file. We can protect that data using Python Decouple #Library. It is the library for separating parameters from the source code.

To make Django secure:

1. Use #SSL.

2. Change the URL.

3. Use 'django-admin-honeypot'.

4. Require stronger passwords.

5. Use two-factor authentication.

6. Use the latest version of Django.

7. Never run 'DEBUG' in production.