Bobcares

Harden the security of Django – Different methods to do it

by | Jan 31, 2021

Need help to harden the security of Django? We can help 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.

As part of our Server Management Services, we assist our customers with several Django queries.

Today, let us see how to harden the security of Django.

 

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

dotenvCopy Code
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.

In this article, we will leverage a security-oriented workflow for Django development by implementing and configuring environment-based settings,

dotenvCopy Code
, 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 Techs suggest having a pre-existing Django project.

 

Harden the security of Django

In this article, we will use the

testsiteCopy Code
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.pyCopy Code
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/settingsCopy Code

This directory will replace the current

settings.pyCopy Code
configuration file.

Once done, we create three Python files:

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

The

development.pyCopy Code
file contains settings we use during development. The
production.pyCopy Code
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.pyCopy Code
settings file contains settings that
development.pyCopy Code
and
production.pyCopy Code
will inherit from. This is to reduce redundancy and to help keep the code cleaner.

These Python files will be replacing

settings.pyCopy Code
, so we will now remove
settings.pyCopy Code
to avoid confusing Django.

We rename

settings.pyCopy Code
to
base.pyCopy Code
with the following command:

$ mv ../settings.py base.pyCopy Code

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-dotenvCopy Code
.

This is a dependency that loads environment variables from a

.envCopy Code
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 ../../Copy Code

Then we install

python-dotenvCopy Code
:

$ pip install python-dotenvCopy Code

Now we need to configure Django to use

dotenvCopy Code
. In order to do this, we will edit,
manage.pyCopy Code
, for development, and
wsgi.pyCopy Code
, 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’)
)Copy Code
Save and close manage.py and then open wsgi.py for editing:
$ nano testsite/wsgi.pyCopy Code

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()Copy Code

The code we have added looks for

.envCopy Code
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 .envCopy Code

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

DJANGO_SETTINGS_MODULE=”testsite.settings.development”Copy Code

Add

.envCopy Code
to
.gitignoreCopy Code
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
.envCopy Code
with settings for that specific environment.

Our Support Techs recommend creating a

.env.exampleCopy Code
to include in the project. It will help to easily create a new
.envCopy Code
wherever we need one.

So, by default Django will use

testsite.settings.developmentCopy Code
, but if we change DJANGO_SETTINGS_MODULE to
testsite.settings.productionCopy Code
, it will start using the production configuration.

 

Step 3: Create Development and Production Settings

Moving ahead, we will open

base.pyCopy Code
and add the configurations to modify for each environment in separate
development.pyCopy Code
and
production.pyCopy Code
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.pyCopy Code

First, we will import from

base.pyCopy Code
– this file inherits settings from
base.pyCopy Code
. 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’),
}
}Copy Code

In this case, the settings specific to development are:

DEBUGCopy Code
, we need this
TrueCopy Code
in development, but not in production; and
DATABASESCopy Code
, 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.pyCopy Code

Production will be similar to

development.pyCopy Code
, but with a different database configuration and
DEBUGCopy Code
set to
FalseCopy Code
:

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’, ”),
}
}Copy Code

For the example database configuration given, we can use

dotenvCopy Code
to configure each of the given credentials, with defaults included.

Hence we have configured the project to use different settings based on

DJANGO_SETTINGS_MODULECopy Code
in
.envCopy Code
.

Given the example settings here, when we set the project to use production settings,

DEBUGCopy Code
becomes
FalseCopy Code
,
ALLOWED_HOSTSCopy Code
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 Engineers does not recommend using any of these settings in the development environment. Hence, we limit these settings to the

production.pyCopy Code
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.pyCopy Code

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 = TrueCopy Code
  • 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-dotenvCopy Code
. 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 .envCopy Code

And add the following line:

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

Then in base.py:

$ nano testsite/settings/base.pyCopy Code

Let us update the SECRET_KEY variable:

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

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 .envCopy Code

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”Copy Code

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

$ nano /testsite/urls.pyCopy Code

Do not forget to replace

your_secret_keyCopy Code
and
very_secret_urlCopy Code
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),
]Copy Code

We can now find the admin login page at

very_secret_url/admin/ instead of just /admin/Copy Code
.

[Stuck with the procedures? We’d be happy to assist you]

 

Conclusion

To conclude, the project now leverages python-dotenv for handling secrets and settings. Today, we saw how our Support Techs go about to harden the security of Django

var google_conversion_label = "owonCMyG5nEQ0aD71QM";

PREVENT YOUR SERVER FROM CRASHING!

Never again lose customers to poor server speed! Let us help you.

Our server experts will monitor & maintain your server 24/7 so that it remains lightning fast and secure.

GET STARTED

0 Comments

Submit a Comment

Your email address will not be published. Required fields are marked *

Speed issues driving customers away?
We’ve got your back!